MOD()是MySQL中计算两数相除余数的取模函数,等价于%运算符;语法为MOD(dividend, divisor),返回值符号与被除数一致,除数为0或任一参数为NULL时返回NULL。

MOD函数在MySQL中怎么用?基本语法和返回值
MOD() 是 MySQL 内置的取模函数,等价于 % 运算符,用于计算两个数相除后的余数。它接受两个参数:MOD(dividend, divisor),其中 dividend 是被除数,divisor 是除数(必须非零)。返回值类型与被除数一致,且符号跟随被除数(例如 MOD(-7, 3) 返回 -1)。
常见错误:直接对 NULL 或字符串字段调用 MOD(),结果为 NULL;除数为 0 会报错 ERROR 1365 (22012): Division by 0。
- 确保目标字段(如用户ID、订单号)是整型或可隐式转为整型(
CAST(id AS SIGNED)) - 避免在
WHERE子句中对未索引字段做MOD()计算,否则无法走索引 - 若字段含负值,需确认业务是否允许负余数——多数负载均衡场景应先用
ABS()归一化
用MOD做分片路由时,为什么不能直接写 MOD(id, 4)?
看似简单:4 台数据库,按 MOD(id, 4) 分配请求。但实际会出问题——ID 不连续、有空缺、或存在负 ID 时,余数分布严重倾斜。比如 ID 序列是 1, 2, 5, 9, 10,MOD(id, 4) 得到 1,2,1,1,2,0 和 3 完全没命中。
真正可行的做法是把 ID 映射到一个稠密、单调递增的序列再取模。常用手段是用 ROW_NUMBER() OVER (ORDER BY id)(MySQL 8.0+)或自增伪列(低版本靠变量模拟),但要注意窗口函数无法在所有上下文中使用(如不能在子查询 WHERE 中直接引用)。
- MySQL 5.7 及更早版本建议用主键哈希替代:例如
MOD(CRC32(CONVERT(id USING utf8mb4)), 4),比直接对 ID 取模更均匀 - 如果 ID 来源可控(如发号器生成),可在写入时就预计算
shard_id = MOD(id, 4)并存为冗余字段,查询时直接WHERE shard_id = ? - 切忌在应用层拼接 SQL 时动态计算
MOD()后再路由——这会让 SQL 注入风险放大,且失去数据库连接池的复用优势
负载均衡查询中 MOD 和 HASH 的性能差异在哪?
MOD() 计算本身极快,但它的瓶颈不在函数执行,而在数据分布不均导致的“长尾”:某台实例承接了 70% 请求。相比之下,哈希(如 CRC32()、MD5() 截断)能更好打散热点,尤其当原始键存在局部聚集(如按时间插入的订单号)时。
实测对比(100 万行测试数据):
SELECT COUNT(*) FROM orders WHERE MOD(order_no, 8) = 0; -- 耗时 120ms,扫描 12.5% 行(理想) SELECT COUNT(*) FROM orders WHERE MOD(CRC32(order_no), 8) = 0; -- 耗时 145ms,但各余数桶记录数标准差下降 60%
-
CRC32()比MD5()快 3–5 倍,且输出是整数,可直接进MOD() - 哈希后取模仍需注意除数大小:除数应为 2 的幂(如 2/4/8/16),避免除法指令开销;非 2 的幂(如 7)会导致 CPU 取模运算变慢
- 如果查询条件里同时有
MOD()和其他索引字段,优化器可能放弃索引——建议把分片字段单独建索引,或组合成联合索引最左前缀
线上灰度切换时 MOD 分片怎么避免数据错乱?
从 4 分片扩到 8 分片时,直接改 MOD(id, 8) 会让一半原有数据“跳槽”,但旧数据还在原库,新写入去新库,读就会漏数据。这不是 MOD() 的 bug,而是分片策略升级的通用难题。
安全做法是引入中间映射层:新增一张 shard_mapping 表,记录每个 ID 区间或哈希段归属的物理库,查询前先查映射表。但这样增加一次 IO。更轻量的方式是“双写+迁移”:
- 新老分片逻辑并行运行:写操作同时落两套库(老
MOD(id, 4)+ 新MOD(id, 8)) - 用后台任务把历史数据按新规则迁移,期间读请求优先走新库,未命中再查旧库(兜底)
- 上线后严禁直接删旧库——哪怕只留 1% 流量,也要保留至少 7 天,因为缓存、重试、异步任务都可能触发旧路径
真正容易被忽略的是时钟同步问题:不同 DB 实例系统时间差超过 1 秒,会导致基于时间戳哈希的分片出现跨库写入,而 MOD() 本身不依赖时间,但周边逻辑常会混用。


















