mysql 5.7及更早版本不支持count(distinct col1, col2),因sql标准中distinct作用于行而传统聚合函数仅接受单值参数,需用concat_ws+ifnull+md5等方案将多列“压平”为唯一指纹。

为什么直接用 COUNT(DISTINCT col1, col2) 会报错?
MySQL 5.7 及更早版本不支持 COUNT(DISTINCT col1, col2) 这种多列去重计数语法,会抛出 ERROR 1241 (21000): Operand should contain 1 column(s)。即使在 MySQL 8.0+ 支持了该语法,部分旧业务库或兼容模式下仍可能失效。
根本原因是:SQL 标准中 DISTINCT 作用于行(row),但传统聚合函数只接受单值表达式作为参数。多列组合需先“压平”为一个可比单元。
用 CONCAT + MD5 生成稳定指纹的实操要点
把多列拼成字符串再哈希,是跨版本通用的替代方案。关键不是“用 MD5”,而是确保拼接方式能区分语义差异(比如 NULL、空字符串、顺序)。
-
CONCAT本身对NULL返回NULL,会导致不同组合被误判为相同——改用CONCAT_WS('|', IFNULL(col1,'[NULL]'), IFNULL(col2,'[NULL]')) - 分隔符必须不可出现在原始数据中,
|比,更安全;若字段含竖线,改用不可见字符如CHAR(0) -
MD5不是必须项,SUM(MD5(...))没意义;只需保证指纹唯一性,MD5或SHA2(...,224)均可,但别用UUID()(每次调用结果不同) - 示例:
SELECT category, COUNT(DISTINCT MD5(CONCAT_WS('|', IFNULL(tag, '[NULL]'), IFNULL(level, '[NULL]')))) AS unique_combo_cnt FROM logs GROUP BY category;
性能与可读性之间的取舍
MD5 方案本质是把计算压力从存储引擎转移到 CPU,尤其在大表上会明显变慢。如果只是临时查数,没问题;若是高频聚合字段,应考虑物化方案。
- 避免在
WHERE或JOIN条件中用MD5(CONCAT(...))——无法走索引,全表扫描不可避免 - 若组合列固定且不多(≤3 列、类型简单),可用
GROUP BY col1, col2再外层套COUNT(*),通常更快:SELECT category, COUNT(*) AS unique_combo_cnt FROM (SELECT DISTINCT category, tag, level FROM logs) t GROUP BY category;
- PostgreSQL 用户可直接用
COUNT(DISTINCT (col1, col2))(注意括号),无需 MD5
容易被忽略的 NULL 和类型隐式转换陷阱
最常踩的坑不是语法,而是数据本身:同一逻辑组合因 NULL 处理或数字/字符串混用导致指纹不一致。
-
CONCAT(1, '1')→'11',CONCAT(11, '')→'11',数值和字符串拼接会丢失类型边界 - 统一转字符串:
CONCAT_WS('|', COALESCE(CAST(col1 AS CHAR), '[NULL]'), COALESCE(CAST(col2 AS CHAR), '[NULL]')) - 时间字段务必格式化:
DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s'),否则NOW()和'2024-01-01 12:00:00'的二进制表示不同
SELECT MD5(...), COUNT(*) FROM t GROUP BY MD5(...) HAVING COUNT(*) > 1。组合唯一性一旦出错,COUNT 结果就全不可信。










