判断存在性为什么用 EXISTS
只需要知道「有没有」时,EXISTS 找到第一行就返回,COUNT(*) 却必须把所有匹配行数完;当满足条件的行很多时,两者代价差一个量级。应用层等价写法是 SELECT 1 … LIMIT 1,只取一行常量。反过来,如果业务确实需要数量(例如展示「共 3 条待处理」),那就必须 COUNT,此时更该关注索引设计与近似统计的取舍,而不是改写成存在性判断。
基本信息
| EXISTS | 短路求值:找到首行即返回,返回布尔值 |
|---|---|
| COUNT(*) | 必须遍历全部匹配行,返回数量 |
| 应用层写法 | SELECT 1 FROM … WHERE … LIMIT 1 |
| 索引要求 | 存在性判断同样需要合适索引,例如 (user_id, amount) 复合索引 |
| 语义差别 | EXISTS 返回是否存在,COUNT 返回具体数量,业务口径不同 |
典型使用场景
- 登录与权限校验:只判断账号、角色是否有效,不需要数量
- 接口的存在性检查:新建前判断编号是否已被占用
- 开关类判断:有没有未读消息、有没有待处理工单
- 批量校验一批 id 是否存在,返回缺失清单
- 数据核对:判断两张表是否存在差异行
数据库内判断存在
数据库内判断存在
-- 短路求值,最省资源
SELECT EXISTS (
SELECT 1 FROM orders WHERE user_id = 42 AND amount > 1000
) AS has_big_order;
-- 应用层只需要「有」的时候,取一行常量即可
SELECT 1 FROM orders WHERE user_id = 42 AND amount > 1000 LIMIT 1;
-- 反例:应用里把结果数一遍再判断 count > 0
SELECT COUNT(*) FROM orders WHERE user_id = 42 AND amount > 1000;批量校验缺失值
批量校验缺失值
# 找出名单里在表中不存在的 id(用于导入前的预检)
SELECT n.id
FROM staging_ids n
LEFT JOIN users u ON u.id = n.id
WHERE u.id IS NULL;
-- 需要返回「是否存在」布尔列的写法
SELECT n.id, (u.id IS NOT NULL) AS exists_flag
FROM staging_ids n
LEFT JOIN users u ON u.id = n.id;几种写法代价对比
| 写法 | 代价 | 适用判断 |
|---|---|---|
| EXISTS | 找到首行即停 | 只需要布尔结果 |
| SELECT 1 … LIMIT 1 | 读取一行即停 | 应用层校验 |
| COUNT(*) | 数完全部匹配行 | 业务真的需要数量 |
| COUNT(*) 且条件命中率高 | 可能接近全表扫描 | 考虑缓存或近似统计 |
EXISTS 依然依赖索引:没有可用索引时,短路也要先扫很多行才能找到第一行;把过滤条件里的列放进复合索引,短路优势才真正发挥出来。
幂等控制里「先 COUNT 再 INSERT」属于竞态写法:两个并发请求可能同时看到 0 条并双双插入,唯一索引或 INSERT IGNORE / ON CONFLICT 才是可靠保障。