结论:before insert或update触发器无法拦截分库分表键合法性,因其仅作用于已路由到的物理表,不参与路由决策,且无法访问分片配置或跨表校验。

触发器根本不能拦截分库分表键的合法性
直接说结论:BEFORE INSERT 或 BEFORE UPDATE 触发器无法验证“分库分表键是否合法”,因为分库分表本身不在 MySQL 单实例内完成——触发器连目标库、目标表在哪都不知道。
所谓“分库分表键”(如 user_id、order_no)的合法性,本质是路由规则问题:它决定这条数据该写进 db01.order_07 还是 db03.order_42。这个决策发生在应用层或中间件(如 ShardingSphere、MyCat),MySQL 实例只收到最终路由后的 SQL,比如 INSERT INTO order_07 (...) VALUES (...)。触发器看到的只是已落地到某张物理表的数据,早已过了“该不该去这张表”的判断点。
常见误解场景:
- 有人在
order_07表上建触发器,检查NEW.user_id % 100 = 7—— 这毫无意义:如果路由错了,数据本就不该来这张表;而触发器拦不住错误路由,只能事后报错或脏写 - 试图用触发器把
INSERT INTO order自动拆成多条写入不同分表 —— MySQL 不允许触发器执行跨表INSERT到未显式声明的表(会报ERROR 1442),且违背分库分表设计原则
真正该拦截什么?分表内字段约束
触发器能且仅能做的是:在**已确定写入某张具体分表的前提下**,校验该行数据是否符合这张表自身的业务规则。例如:
假设你按 user_id % 4 水平分了 user_0 ~ user_3 四张表,每张表只应存对应余数的用户。此时可在 user_0 上建触发器,强制校验:
DELIMITER //
CREATE TRIGGER tr_user_0_check_shard_key
BEFORE INSERT ON user_0
FOR EACH ROW
BEGIN
IF NEW.user_id % 4 != 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'user_id does not belong to this shard';
END IF;
END//
DELIMITER ;
但注意:
- 这属于“兜底防御”,不是路由逻辑,不能替代中间件或 DAO 层的分片计算
- 必须为每张分表单独创建对应触发器,新增分表时极易遗漏
- 若应用层写错表(比如把
user_id=5写进user_0),触发器才起作用;但写对表却传错user_id值(如传user_id=NULL),仍需靠字段NOT NULL或CHECK约束 - MySQL 8.0.16+ 支持
CHECK约束,比触发器更轻量、更标准,优先用CHECK (user_id % 4 = 0)
为什么别依赖触发器做分片校验?性能与维护双坑
即使硬上触发器做分片键校验,也会立刻暴露三个硬伤:
-
NEW和OLD无法访问其他表,所以不能查配置表动态加载分片规则(比如从shard_config读取当前分片数),规则写死在触发器里,改规则就得重装所有触发器 - 每次
INSERT/UPDATE都多一次模运算 + 条件判断,QPS 高时延迟明显上升,尤其当分片数是质数(如 97)时,CPU 计算开销不可忽略 - 主从复制下,触发器在从库串行执行,若主库批量写入 1000 行,从库要顺序跑 1000 次触发器逻辑,加剧
Seconds_Behind_Master
真正的分片键合法性,必须在数据进入数据库前由中间件或 ORM 层完成。触发器在这里唯一合理的作用,是防止误操作直连 DB 导致数据错位——但它救不了架构设计缺陷。











