前言
在mysql学习和开发的过程中,新手很容易写出“能跑但有问题”的sql:要么性能极差,要么结果错误,甚至引发数据误删的严重故障。这些错误往往不是语法错误,而是逻辑错误、性能错误或安全错误,很难被发现,但危害极大。
本文整理了8种最常见的新手sql错误用法,每种错误都配详细的“错误示例”、“危害分析”、“正确写法”和“避坑指南”,帮助你从入门开始就养成良好的sql习惯,避免踩坑。
错误一:滥用 select *,只图省事不看后果
错误用法
很多新手图省事,查询时直接写select *,不管需要多少字段:
-- 错误写法:查询所有字段 select * from user_info where city = '武汉';
为什么错
- 浪费磁盘io和网络带宽:查询了很多不需要的字段(比如大字段
content、avatar),增加了磁盘读取和网络传输的开销; - 无法使用覆盖索引:如果只查询需要的字段,且这些字段都在联合索引中,可以使用“覆盖索引”,无需回表,性能提升数倍;但
select *必须回表查整行数据,性能差; - 表结构变更风险:如果表结构新增或删除了字段,
select *的结果会变化,可能导致应用层报错。
正确用法
只查询业务需要的字段:
-- 正确写法:只查询需要的字段 select id, username, phone, age from user_info where city = '武汉';
如果这些字段在联合索引(idx_city_username_phone_age)中,就是覆盖索引,无需回表,性能极佳。
避坑指南
- **永远不要写select ***,除非你真的需要所有字段;
- 写sql前先想清楚:业务到底需要哪些字段?只查这些字段;
- 用explain查看执行计划,如果
extra列有using index,说明用到了覆盖索引,很好。
错误二:不带 where 条件的 update/delete,高危操作!
错误用法
这是最危险的错误,一不小心就会全表更新或删除:
-- 错误写法:不带where条件的update,全表更新! update user_info set age = 28; -- 错误写法:不带where条件的delete,全表删除! delete from user_info;
为什么错
- 全表操作:不带where条件,会更新/删除表中的所有数据,无法回滚(除非在事务中);
- 线上故障:如果在生产环境执行,会导致所有数据丢失或错误,引发严重的线上事故,甚至需要离职赔偿。
正确用法
必须带where条件:
-- 正确写法:带where条件,只更新指定行 update user_info set age = 28 where id = 1; -- 正确写法:带where条件,只删除指定行 delete from user_info where id = 1;
执行前先select验证:执行update/delete前,先用select查看where条件匹配的行数,确认无误后再执行:
-- 先select验证:查看匹配的行数 select count(*) from user_info where id = 1; -- 确认只有1行后,再执行update/delete
生产环境用逻辑删除替代物理删除:不要直接delete,用is_deleted字段标记删除:
-- 逻辑删除:更新is_deleted为1,而不是delete update user_info set is_deleted = 1 where id = 1;
避坑指南
- update/delete必须带where条件,不带条件绝对不执行;
- 执行前先select验证,确认匹配的行数和数据;
- 生产环境开启sql审核,禁止不带where的update/delete;
- 尽量用逻辑删除,避免物理删除,误删后还能恢复。
错误三:like 通配符在开头,索引失效全表扫描
错误用法
用like模糊查询时,把通配符%放在开头:
-- 错误写法:通配符在开头,索引失效 select * from user_info where username like '%张三%'; -- 更糟:通配符只在开头 select * from user_info where username like '%张三';
为什么错
mysql的联合索引遵循最左前缀原则,like查询只有通配符在结尾时才能用到索引:
like '张三%'能用索引(前缀匹配);like '%张三'索引失效(后缀匹配);like '%张三%'索引失效(中间匹配)。
通配符在开头时,mysql无法利用索引的有序性,只能全表扫描,性能极差。
正确用法
尽量用前缀匹配:
-- 正确写法:通配符在结尾,能用索引 select * from user_info where username like '张三%';
如果必须用中间/后缀匹配,用全文索引或elasticsearch:
如果业务必须用%张三%这样的模糊查询,不要用like,改用:
- mysql的全文索引(fulltext index);
- 或者把数据同步到elasticsearch,用es做模糊查询,性能更好。
避坑指南
- like查询尽量用前缀匹配,通配符只放结尾;
- 用explain查看执行计划,如果
type列是all,说明全表扫描,索引失效; - 必须用中间/后缀匹配时,用全文索引或es,不要用like。
错误四:在索引列上用函数/表达式,索引白白浪费
错误用法
在索引列上使用函数(比如year()、date())或表达式(比如id + 1):
-- 错误写法:在索引列create_time上用year()函数,索引失效 select * from user_info where year(create_time) = 2026; -- 错误写法:在索引列id上用表达式,索引失效 select * from user_info where id + 1 = 2;
为什么错
mysql的索引是对列的原始值建立的b+树,如果在列上用了函数或表达式,索引的有序性就被破坏了,优化器无法使用索引,只能全表扫描。
正确用法
把函数/表达式移到等号的右边,让索引列保持“干净”:
-- 正确写法:把year()移到右边,用范围查询,能用索引 select * from user_info where create_time >= '2026-01-01' and create_time < '2027-01-01'; -- 正确写法:把表达式移到右边,id保持干净,能用索引 select * from user_info where id = 2 - 1;
如果必须在列上用函数,可以创建函数索引(mysql 8.0+支持):
-- 创建函数索引 create index idx_year_create_time on user_info ((year(create_time))); -- 现在可以用year()查询了,能用到函数索引 select * from user_info where year(create_time) = 2026;
避坑指南
- 永远不要在索引列上用函数/表达式,保持索引列“干净”;
- 把函数/表达式移到等号右边,用范围查询替代;
- 如果必须用函数,mysql 8.0+可以创建函数索引;
- 用explain查看执行计划,确认索引是否生效。
错误五:隐式类型转换,索引失效还可能查错数据
错误用法
查询时,字段类型和参数类型不一致,导致隐式类型转换:
-- 错误写法:phone是varchar类型,却用数字13800138000查询,隐式类型转换 select * from user_info where phone = 13800138000; -- 错误写法:id是bigint类型,却用字符串'1'查询,隐式类型转换 select * from user_info where id = '1';
为什么错
索引失效:隐式类型转换会破坏索引的有序性,优化器无法使用索引,只能全表扫描;
查错数据:隐式类型转换可能导致查询结果错误。比如phone是varchar,phone = 13800138000会把phone的字符串转换为数字,'13800138000a'这样的字符串也会被转换为13800138000,导致查错数据。
正确用法
保持字段类型和参数类型一致:
-- 正确写法:phone是varchar,用字符串'13800138000'查询 select * from user_info where phone = '13800138000'; -- 正确写法:id是bigint,用数字1查询 select * from user_info where id = 1;
避坑指南
- 保持字段类型和参数类型一致,避免隐式类型转换;
- 建表时,选择合适的数据类型:手机号、身份证号用
varchar,不要用bigint; - 用explain查看执行计划,如果
type列是all,且字段类型不一致,可能是隐式类型转换导致的。
错误六:滥用 not in / <>,索引失效还可能结果错误
错误用法
很多新手喜欢用not in或<>(不等于)来排除数据:
-- 错误写法:not in,可能索引失效
select * from user_info where city not in ('武汉', '北京');
-- 错误写法:<>,可能索引失效
select * from user_info where age <> 28;
为什么错
索引失效:not in和<>属于“负向查询”,mysql优化器通常不会选择索引,而是全表扫描,性能差;
not in包含null时结果错误:如果not in的列表中有null,整个查询会返回空结果,因为null的三值逻辑导致的。
正确用法
尽量用正向查询替代:如果业务允许,用in替代not in,用=替代<>;
如果必须用负向查询,用exists或left join is null替代:
-- 用not exists替代not in,性能更好,且不受null影响 select * from user_info u where not exists ( select 1 from exclude_city e where e.city = u.city ); -- 用left join + is null替代not in select u.* from user_info u left join exclude_city e on u.city = e.city where e.city is null;
not in列表中绝对不要包含null:
-- 错误:not in列表中有null,返回空结果
select * from user_info where city not in ('武汉', null);
-- 正确:not in列表中没有null
select * from user_info where city not in ('武汉', '北京');
避坑指南
- 尽量避免用not in和<>,优先用正向查询;
- 如果必须用负向查询,用not exists或left join is null替代;
- not in列表中绝对不要包含null,否则结果错误;
- 用explain查看执行计划,确认是否用到索引。
错误七:用 order by rand() 随机查询,性能极差
错误用法
很多新手用order by rand()来随机查询数据:
-- 错误写法:order by rand(),全表扫描+全表排序,性能极差 select * from user_info order by rand() limit 10;
为什么错
order by rand()的执行逻辑是:
- 为表中的每一行生成一个随机数;
- 按照随机数对所有行进行排序(通常是文件排序filesort);
- 取前n条。
如果表有100万行,就需要生成100万个随机数,然后对100万行进行排序,磁盘io和cpu开销极大,查询耗时可能达到秒级甚至分钟级。
正确用法
方案一:利用自增主键id范围随机(推荐,性能最高)
如果表有连续的自增主键id:
-- 步骤1:获取id的最小值和最大值 select min(id) as min_id, max(id) as max_id from user_info; -- 步骤2:在应用层生成10个不重复的随机id(比如123, 456...) -- 步骤3:通过id精准查询 select * from user_info where id in (123, 456, 789, ...);
方案二:覆盖索引+order by rand()(折中方案)
如果没有连续的自增主键,先通过覆盖索引随机查id,再回表:
-- 先通过覆盖索引随机查10个id,排序的数据量小 select t.* from user_info t inner join ( select id from user_info order by rand() limit 10 ) tmp on t.id = tmp.id;
避坑指南
- 绝对不要直接用select * order by rand(),性能极差;
- 优先用自增主键id范围随机,性能最高;
- 如果没有连续主键,用覆盖索引+order by rand(),减少排序的数据量;
- 大表随机查询,考虑用redis缓存id列表,在应用层随机。
错误八:忽略 null 值的三值逻辑,结果错误
错误用法
很多新手对null的三值逻辑不了解,写出错误的sql:
-- 错误写法:用= null判断null,永远返回false select * from user_info where email = null; -- 错误写法:not in列表中有null,返回空结果 select * from user_info where id not in (1, 2, null); -- 错误写法:count(email)会忽略null值,结果不对 select count(email) from user_info;
为什么错
mysql的逻辑判断有三种结果:true、false、unknown,而null代表“未知”:
= null:结果是unknown,不会返回任何行;not in (..., null):结果是unknown,整个查询返回空;count(列名):会忽略null值,只统计非null的行数;count(*)才会统计所有行数。
正确用法
用is null / is not null判断null:
-- 正确写法:用is null判断null select * from user_info where email is null; -- 正确写法:用is not null判断非null select * from user_info where email is not null;
not in列表中不要包含null:
-- 正确:not in列表中没有null select * from user_info where id not in (1, 2, 3);
区分count(*)和count(列名):
-- count(*):统计所有行数,包括null值 select count(*) from user_info; -- count(email):统计email非null的行数 select count(email) from user_info;
避坑指南
- 永远不要用= null或<> null,用is null / is not null;
- not in列表中绝对不要包含null;
- 区分count(*)和count(列名):统计所有行数用count(*),统计非null行数用count(列名);
- 建表时,尽量给字段设置
not null和默认值,避免null值带来的问题。
总结:新手sql避坑的5个核心习惯
看完这8种错误,我们可以总结出新手sql避坑的5个核心习惯:
- 永远不写select ,只查需要的字段,尽量用覆盖索引;
- update/delete必须带where条件,执行前先select验证,生产环境用逻辑删除;
- 保持索引列“干净”:不用函数/表达式、不用隐式类型转换、like通配符只放结尾;
- 尽量用正向查询:避免not in/<>,用exists/left join替代;
- 重视null值的三值逻辑:用is null判断null,not in列表不含null,区分count(*)和count(列名)。
最后,写sql后一定要用explain查看执行计划,重点看type(访问类型)、key(实际用到的索引)、rows(预计扫描的行数)、extra(额外信息),确认sql的性能符合预期,避免踩坑。
以上就是8个mysql常见的新手sql错误用法详解的详细内容,更多关于mysql新手sql错误用法的资料请关注代码网其它相关文章!
发表评论