字符串比较按字符编码顺序逐位进行,而非字面长短;min()和max()对字符串求极值时严格遵循此规则,如min('apple','application','banana')返回'apple',因首字符'a'编码最小。

字符串比较不是按“字面长短”,而是逐字符ASCII/Unicode顺序
MIN() 和 MAX() 对字符串字段求极值时,根本不是看“哪个更短”或“哪个更常用”,而是严格按字符编码顺序逐位比对。比如 MIN('apple', 'application', 'banana') 返回 'apple',不是因为短,而是因为首字符 'a' 'b';若首字符相同,就比第二个字符,依此类推。
实际中容易踩的坑是误以为中文会按笔画或常用度排序——其实数据库默认按拼音首字母(UTF-8 下对应 Unicode 码点)比较。例如 '张三' 和 '李四',因 '李'(U+674E)码点小于 '张'(U+5F20),所以 MIN() 会返回 '李四'。
- MySQL 默认使用表/列定义的 collation(如
utf8mb4_0900_as_cs区分大小写,utf8mb4_general_ci不区分)影响结果 - PostgreSQL 强制按
LC_COLLATE环境设置排序,不支持运行时切换规则 - SQL Server 若用
Latin1_General_CI_AS,中文仍走拼音序;但用Chinese_PRC_CI_AS才真正按汉字字典序
当字符串里混着数字,MAX/MIN 可能返回意料之外的结果
如果字段存的是形如 'v1.10'、'v1.2'、'v2.1' 的版本号,直接 MAX(version) 会返回 'v2.1'(正确),但 'v1.10' 会排在 'v1.2' 前面(因为 '1' '2'),导致 MAX() 错判为 'v1.2'。
这不是函数 bug,而是字符串比较的必然行为。解决思路不是改函数,而是标准化输入:
- 提前用
LPAD(SUBSTRING_INDEX(version, '.', 1), 10, '0')(MySQL)补零对齐主版本 - 拆成多字段存储:major、minor、patch,再用
MAX(major, minor, patch)组合判断 - 避免在字符串字段里存结构化数据;版本号、IP 地址等应拆列或转为整数存储
NULL 值被自动忽略,但全 NULL 会返回 NULL
MIN() 和 MAX() 遇到 NULL 一律跳过,这点和 SUM() 一致。但如果整列都是 NULL,函数不报错,而是安静地返回 NULL —— 这在报表里可能表现为“空单元格”,容易被当成无数据,实则是数据质量异常。
验证是否存在全 NULL 风险,建议加一层防御:
- 用
COUNT(column)对比COUNT(*),差值就是该列 NULL 行数 - 必要时用
COALESCE(MIN(col), 'N/A')显式兜底,但注意类型要兼容(字符串列不能COALESCE成数字) - 在 ETL 或应用层做非空校验,比在 SQL 里硬补更可靠
想查“最大值那行的完整记录”?别直接 SELECT * + MAX()
常见错误写法:SELECT *, MAX(created_at) FROM orders —— 这语法在标准 SQL 中非法,MySQL 5.7 以前可能侥幸通过(依赖 sql_mode 宽松模式),但返回的其他字段值是随机的,不可靠。
真正安全的做法只有两种:
- 子查询:
SELECT * FROM orders WHERE created_at = (SELECT MAX(created_at) FROM orders) - 窗口函数(推荐,尤其多条并列最大值时):
SELECT * FROM (SELECT *, RANK() OVER (ORDER BY created_at DESC) rnk FROM orders) t WHERE rnk = 1
注意:如果 created_at 有重复,子查询方式会返回全部并列记录;而 RANK() 可控,ROW_NUMBER() 则只取一行——选哪个取决于业务是否允许“并列”。










