MOD 取模是什么
MOD(a, b) 返回 a 除以 b 的余数,MySQL 与 PostgreSQL 同时支持 % 运算符。用途集中在分桶与分组:按 id 取模做分表分片路由,按散列值取模做均匀抽样,用 MOD(x, 2) 判断奇偶。必须记住的细节是负数取模——结果符号与被除数一致(截断除法),不是数学意义上的非负余数:MOD(-1, 3) 得到 -1 而不是 2。另一个差异在除数为零时:MySQL 返回 NULL,PostgreSQL 直接报 division by zero。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL / Oracle / SQL Server,函数名各不相同 |
|---|---|
| 写法 | MySQL 与 PostgreSQL 都支持 MOD(a, b) 与 a % b;Oracle 用 MOD 函数 |
| 负数规则 | 结果符号与被除数一致:MOD(-1, 3) 为 -1 |
| 除数为零 | MySQL 返回 NULL,PostgreSQL 报 division by zero |
| 数据类型 | 支持整数与 DECIMAL;浮点数取模存在精度问题,业务计算应避免 |
典型使用场景
- 分表分片路由:按用户 id 取模决定落到哪张分表
- 均匀抽样:WHERE MOD(id, 10) = 0 取约十分之一的数据
- 奇偶判断:交替排版、A/B 分流的分组依据
- 循环编号:配合行号生成 1 到 N 循环的序号
- 时间分组:按天序号取模做周期性任务编排
基础用法
基础用法
SELECT MOD(10, 3) AS m1, # 1
10 % 3 AS m2, # 1:运算符写法两库都支持
MOD(-1, 3) AS m3, # -1:符号跟被除数走
MOD(10, 0) AS m4; # MySQL 返回 NULL,PostgreSQL 直接报错
# 均匀抽样:取约十分之一的用户(分布均匀,但会全表扫描)
SELECT * FROM users WHERE MOD(id, 10) = 0;
# 分片路由:算出应落到哪张分表
SELECT CONCAT('orders_', MOD(user_id, 4)) AS shard_table
FROM users WHERE id = 1;需要非负余数时
需要非负余数时
# 负数会得到负余数,需要 0 到 n-1 的取值范围时做一次修正
SELECT MOD(MOD(-1, 3) + 3, 3) AS positive_mod; # 2
-- 分片路由前先归一化,避免负数主键算出负分片号
SELECT CONCAT('orders_', MOD(MOD(user_id, 4) + 4, 4)) AS shard FROM users;
-- 用取模做循环行号(配合行号函数)
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn,
MOD(ROW_NUMBER() OVER (ORDER BY id) - 1, 5) + 1 AS cycle_no
FROM orders;常见表达式结果
| 表达式 | MySQL | PostgreSQL |
|---|---|---|
| MOD(10, 3) | 1 | 1 |
| 10 % 3 | 1 | 1 |
| MOD(-1, 3) | -1 | -1 |
| MOD(10, 0) | NULL | division by zero 报错 |
| MOD(10.5, 3) | 1.5 | 1.5 |
取模结果与除法的取整方式绑定:MySQL 与 PostgreSQL 都用截断除法,所以余数符号跟被除数;要统一成非负余数,用 MOD(MOD(x, n) + n, n) 这一层包装即可。
在 WHERE 里对列取模(MOD(id, 10) = 0)会让该列的索引失效,大表上等于全表扫描;抽样场景应考虑主键范围随机的写法,或 PostgreSQL 的 TABLESAMPLE。
判断奇偶别写成 MOD(x, 2) = 1:负数被除数的余数是 -1 而不是 1,漏掉负值行;要覆盖全部奇数应写成 MOD(x, 2) <> 0,或先把值归一化到非负区间。