最近在重构一个老项目时,被一段SQL查询卡得死死的,排查下来才发现是 IN 和 NOT IN 惹的祸。虽然这两个关键字看着挺顺手,写起来也方便,但我今天就来说说为啥真正的开发老司机都悄悄抛弃了它们。

性能差到惊人:一个血的教训
前段时间我在处理用户数据同步时遇到个情况:users表和user_profiles表都是90万条数据左右,大概300MB,完全算不上大表。结果我随手写了句:
SELECT * FROM users WHERE mobile NOT IN (SELECT mobile FROM user_profiles)
这条查询直接把我整懵了...跑了几分钟还没出结果!检查后发现mobile字段在两个表都建了索引,类型也完全一致。执行explain后才知道,原来NOT IN这种操作方式没走索引,难怪这么慢。改成了EXISTS后,整个世界都清静了:
SELECT * FROM users
WHERE NOT EXISTS (SELECT mobile FROM user_profiles WHERE users.mobile = user_profiles.mobile)
仅仅十几秒就跑完了,简直天壤地别!
最坑的是:它不会明确报错,让你吃暗亏
除了慢,IN/NOT IN还有个更恶心的问题:容易写错但不会报错,查出来的结果还看着像那么回事。我用两个简单的表来演示:
CREATE TABLE employees (emp_id INT);
CREATE TABLE departments (dept_id INT);
INSERT INTO employees (emp_id) VALUES (1),(2),(3);
INSERT INTO departments (dept_id) VALUES (1),(2);
如果我想找出在departments表中存在的员工ID,正常应该这么写:
SELECT emp_id FROM employees
WHERE emp_id IN (SELECT dept_id FROM departments)
结果很正常:返回1和2。但假如我手抖写成了:
SELECT emp_id FROM employees
WHERE emp_id IN (SELECT emp_id FROM departments)
注意看,我把子查询里的dept_id写成了emp_id。魔幻的是,这句SQL居然不报错!直接返回了1、2、3所有数据!更扯的是,如果单独执行SELECT emp_id FROM departments肯定会报错"列名'emp_id'无效",但放在IN子查询里就不报错了,这也太阴险了吧。
老司机都用啥替代方案?
既然IN和NOT IN这么不靠谱,咱们就用更稳的方案:
方案一:EXISTS/NOT EXISTS
-- 代替IN
SELECT * FROM employees
WHERE EXISTS (SELECT 1 FROM departments WHERE departments.dept_id = employees.emp_id);
-- 代替NOT IN
SELECT * FROM employees
WHERE NOT EXISTS (SELECT 1 FROM departments WHERE departments.dept_id = employees.emp_id);
方案二:JOIN大法
-- 代替IN
SELECT employees.emp_id FROM employees
INNER JOIN departments ON departments.dept_id = employees.emp_id;
-- 代替NOT IN
SELECT employees.emp_id FROM employees
LEFT JOIN departments ON departments.dept_id = employees.emp_id
WHERE departments.dept_id IS NULL;
这两种方法不仅性能好,而且对NULL的处理也更加合理,不会搞出莫名其妙的结果。
说真的,少用IN和NOT IN吧
看完这些例子,我是再也不敢随便用IN和NOT IN了。虽然它们看起来简单直观,写起来也省事,但隐藏的坑实在太多。特别是在处理大量数据的时候,性能差异简直就是天壤之别。如果你们团队的代码库里还有这种写法,赶紧改掉吧!别等到生产环境出了问题才来查原因。各位,你们平时用哪种方式?有没有踩过类似的坑?
4640

被折叠的 条评论
为什么被折叠?



