在触发器中获取INSERT的租户ID,须确保表含tenant_id字段,通过NEW.tenant_id访问;MySQL用BEFORE INSERT赋值,PostgreSQL用current_setting('app.tenant_id')动态获取,并校验非空防篡改。

触发器里怎么拿到 INSERT 的租户ID?
触发器无法直接访问 SQL 语句里的字段值,必须通过 NEW(行级 BEFORE INSERT 或 AFTER INSERT)或 OLD 引用当前行数据。前提是租户ID字段(比如 tenant_id)真实存在于被插入的表结构中——如果应用层把租户ID藏在 session 变量、注释或 HTTP header 里,触发器根本看不见。
常见错误现象:ERROR: record "new" has no field "tenant_id",说明表没定义该字段;或者字段名写错成 tenantid 或 TENANT_ID(注意大小写敏感)。
实操建议:
- 确认目标表含
tenant_id字段,类型建议为TEXT或CHAR(32),避免用SERIAL或UUID类型导致分区键不一致 - 触发器必须声明为
FOR EACH ROW,STATEMENT级别拿不到NEW - PostgreSQL 中可直接写
NEW.tenant_id;MySQL 8.0+ 支持类似语法,但旧版需用NEW.`tenant_id`防关键字冲突
如何动态路由到 tenant_123 这类分区表?
触发器不能拼接表名后直接 INSERT INTO tenant_ || NEW.tenant_id——SQL 解析期就要求表名确定,运行时拼接会报 relation does not exist。必须用动态 SQL(EXECUTE)配合 format() 构造语句。
使用场景:你已按租户预建好分区表(如 orders_tenant_a、orders_tenant_b),且主表是空壳(只存元数据或用于统一查询视图)。
实操建议:
- PostgreSQL 示例:
EXECUTE format('INSERT INTO orders_%s VALUES ($1.*)', NEW.tenant_id) USING NEW;注意$1.*是 PostgreSQL 特有语法,把整行传入目标表 - MySQL 不支持
USING NEW,得显式列出所有字段:SET @sql = CONCAT('INSERT INTO orders_', NEW.tenant_id, ' (id, amount, tenant_id) VALUES (?, ?, ?)'); PREPARE stmt FROM @sql; EXECUTE stmt USING NEW.id, NEW.amount, NEW.tenant_id; - 务必提前验证目标表存在,否则触发器失败会导致原 INSERT 回滚——可在
EXECUTE前加SELECT 1 FROM pg_tables WHERE tablename = ...(PG)或查information_schema.tables(MySQL)
为什么 INSERT 后原表还多出一条脏数据?
这是最常踩的坑:触发器写在主表上,又没阻止原 INSERT 执行,结果数据既进了分区表,又落到了主表(而主表通常没主键/索引,变成垃圾数据池)。
性能影响:主表持续膨胀会拖慢 VACUUM、影响 ANALYZE 统计信息,后续查询走错执行计划。
实操建议:
- 用
BEFORE INSERT触发器,在写入前拦截并重定向,最后RETURN NULL(PG)或LEAVE(MySQL)阻止默认行为 - 绝对不要用
AFTER INSERT再往分区表插——那是双写,且事务不可控 - 主表应设为
UNLOGGED(PG)或禁用索引(MySQL),减少写开销;但要注意主表不能是唯一数据源 - 如果业务允许,干脆删掉主表,让应用直连对应分区表,触发器只作兜底兼容
分区表名含特殊字符或大小写怎么办?
租户ID来自外部系统时,可能含破折号、点号、大写字母(如 acme-inc.v2),直接拼进表名会语法报错或找不到表。
容易被忽略的地方:表名合法性检查不是触发器责任,而是建表环节就要标准化。临时在触发器里处理,只会让逻辑变脆。
实操建议:
- 建表时统一用下划线替换非法字符:
REPLACE(REPLACE(NEW.tenant_id, '-', '_'), '.', '_') - 强制小写:
LOWER(NEW.tenant_id),避免TenantA和tenanta写入不同表 - 加长度限制和白名单校验,防止 SQL 注入(虽然
format()有防注入机制,但tenant_id若来自用户输入,仍建议正则过滤:NEW.tenant_id ~ '^[a-z0-9_]{3,32}$') - 别依赖触发器做租户隔离的唯一防线——应用层必须先校验租户权限,否则恶意用户可能伪造
tenant_id写入他人分区

















