动态分表路由通过元数据表report_table_strategy存储租户分片策略,查询时先查配置再拼sql,须白名单校验、参数化传参、quotename()防注入,并缓存元数据。

租户ID查元数据表决定目标表名和分区条件
动态分表路由不是靠硬编码表名,而是把分表策略存进数据库——比如建一张 report_table_strategy,字段含 tenant_id、sharding_type(HASH/RANGE)、shard_count、partition_granularity(DAY/MONTH)等。查询时先 SELECT 这张表拿到当前租户的配置,再据此拼接 SQL。
关键点在于:所有元数据值必须走白名单校验(比如 sharding_type IN ('HASH', 'RANGE')),不能直接拼进 SQL 字符串;分区值(如日期)必须用参数化方式传入,否则等于开 SQL 注入后门。
-
tenant_id是主键或唯一索引,确保单次查询只命中一条策略记录 - 若租户未配置,默认行为应明确(如报错、走公共表、或 fallback 到默认分片数)
- 元数据表本身要加缓存(如应用层本地缓存 5 分钟),避免每次查询都查库
用 QUOTENAME() 和参数化 WHERE 防止注入和语法错误
拼接动态表名时,MySQL 用反引号、SQL Server 用 QUOTENAME(),PostgreSQL 用双引号,目的都是防关键字冲突和非法字符。WHERE 条件绝不能字符串拼接值,而要拆成独立变量 + EXEC sp_executesql(SQL Server)或 PREPARE/EXECUTE(MySQL)。
例如在 SQL Server 存储过程中,@TableName 经 QUOTENAME(@TableName) 处理后才用于 FROM 子句;时间范围条件写成 WHERE report_date >= @StartDate AND report_date ,<code>@StartDate 和 @EndDate 作为参数传入,不拼字符串。
- MySQL 中
CONCAT('SELECT * FROM `', @table_name, '` WHERE ...')必须配合PREPARE stmt FROM @sql; EXECUTE stmt USING @start_date, @end_date; - SQL Server 中
sp_executesql的参数定义字符串(如N'@StartDate DATE')和值列表(如@StartDate)必须严格对应 - 别忽略时区:
@StartDate应基于业务时区生成,不是简单用GETDATE()
按租户哈希或范围计算子表名的典型逻辑
拿到元数据后,下一步是算出具体子表名。常见两种方式:
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
哈希路由:对 tenant_id 做哈希再取模,比如 MOD(ABS(HASHBYTES('MD5', CAST(@tenant_id AS VARCHAR))), @shard_count) + 1(SQL Server),结果为 1~4,则表名可能是 report_tenant_1 ~ report_tenant_4;
范围路由:若元数据中 sharding_type = 'RANGE',且 range_start = 1000、range_step = 1000,则 @tenant_id / 1000 + 1 得到分片编号,表名类似 report_range_2。
- 哈希值必须用确定性函数(如
HASHBYTES),不能用NEWID()或RAND() - 范围计算注意整除和边界,
tenant_id = 1000应归入第 1 片还是第 2 片,逻辑必须和写入时完全一致 - 表名建议统一加前缀(如
report_)+ 后缀(如_tenant_3),避免和系统表冲突
为什么不能在触发器里做租户路由
有人想在 INSERT 触发器里根据 NEW.tenant_id 自动路由到不同子表,这在 MySQL 中不可行:触发器不支持动态表名,INSERT INTO CONCAT('report_', NEW.tenant_id) 会直接报错;即使硬编码分支(如 IF NEW.tenant_id = 1 THEN INSERT INTO report_tenant_1 ...),也意味着每新增一个租户就得改触发器,运维爆炸。
更严重的是,触发器内无法控制事务边界,一旦子表写失败,主表已插入,数据不一致风险极高。正确做法是把路由逻辑前置到存储过程或应用层,确保“写入即路由”,且整个操作在同一个事务中完成。
真正该用触发器的场景极少,比如审计日志的自动打标;租户级分表路由这种核心路径,必须由可控、可测、可灰度的上层逻辑接管。










