MAX() 返回 '999' 而非 28000 是因 VARCHAR 按字典序比较,首字符 '9' > '2' > '1';根本解法是 CAST 转数值或改列类型为 BIGINT,而非依赖排序规则。

MAX() 在 VARCHAR 上返回“9”而不是“28000”?先查排序规则
不是函数坏了,是 MySQL / PostgreSQL / Oracle 默认按字符序(collation)比较字符串,而非数值大小。比如 bill_no 是 VARCHAR,值为 '10000'、'28000'、'999',MAX() 会返回 '999'——因为首字符 '9' > '2' > '1',后续位根本不会比。
关键不是字段有没有数字,而是当前列的 COLLATION 是否启用二进制或数值感知排序:
-
utf8mb4_0900_as_cs(大小写敏感)仍是字典序,不解决数值问题 -
utf8mb4_bin按字节比较,对纯数字字符串也无效('10'的字节仍小于'9') - 没有“数值 collation”,别指望靠改排序规则让
MAX()算出 28000
CAST 和 +0 哪个更安全?看数据质量
两者都能强制转数值,但行为差异明显:
-
MAX(CAST(bill_no AS UNSIGNED)):遇到非数字字符(如'BIL-28000'或前导空格)直接报错或转成 0,适合数据干净、可清洗的场景 -
MAX(bill_no + 0):MySQL 特有隐式转换,会跳过开头非数字部分('ABC123'→ 123,'123XYZ'→ 123),但可能掩盖脏数据,调试困难 - Oracle 必须用
TO_NUMBER(),且需ON CONVERSION ERROR处理异常(12c+)
线上环境建议优先用 CAST + 预检查询,例如:
SELECT bill_no FROM central_billing WHERE branch_code = ? AND bill_no REGEXP '^[0-9]+$' = 0 LIMIT 5;
带前缀的编码(如 XHL999)怎么取最大数值部分?
不能只靠 MAX(),得先分离字母和数字。常见错误是硬写 SUBSTRING(code, 4),但前缀长度不固定时必翻车。
- MySQL 8.0+ 可用正则提取数字:
MAX(CAST(REGEXP_SUBSTR(code, '[0-9]+') AS UNSIGNED)) - PostgreSQL 用
MAX((regexp_match(code, '\d+'))[1]::int) - Oracle 需配合
REGEXP_REPLACE(code, '\D', '')清除非数字再TO_NUMBER - 注意:所有正则提取都假设数字部分唯一且连续;若存在
XHL12A34,结果就是 12,不是 1234
为什么加了 CAST 还慢?索引失效是真问题
MAX(CAST(bill_no AS UNSIGNED)) 无法走 bill_no 原始列索引——函数包裹导致索引失效,全表扫描不可避免。
- 临时方案:建函数索引(MySQL 8.0.13+、PostgreSQL 8.3+、Oracle 8i+):
CREATE INDEX idx_billno_num ON central_billing ((CAST(bill_no AS UNSIGNED))) - 长期方案:业务确认无字母前缀后,直接
ALTER TABLE ... MODIFY COLUMN bill_no BIGINT,并补上NOT NULL和INDEX - 切勿在 WHERE 中混用:
WHERE CAST(bill_no AS UNSIGNED) > 10000同样无法利用索引
最易被忽略的一点:就算你修复了类型、加了索引、写了正确 SQL,如果应用层缓存了旧查询结果(比如 MyBatis 的二级缓存没清),看到的还是错的 10000——先绕过缓存直连 DB 验证,再排查逻辑。

















