LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

86个SQL常见陷阱:SQL常用语句避坑指南

admin
2025年9月27日 2:7 本文热度 5001

SQL作为数据管理的通用语言,看似简单其实处处暗藏玄机。无论是我们初学者还是那些资深大佬,都可能在不经意间踏入各种SQL陷阱,导致SQL查询结果异常、性能瓶颈甚至数据安全隐患。下面我们一起从SQL的基础语法、索引优化、查询技巧、事务处理、表结构设计、JOIN操作、JSON处理、架构运维、安全权限到高级特性,以表格的形式简单地介绍一下86个SQL常见陷阱(后面我们再一起详细拆解)。每个SQL陷阱,我们都给出了核心原因并提供了实用的避坑方法,同时特别关注了不同数据库间的行为差异。掌握了这些知识点,不仅能帮我们避免常见错误,还能帮我们提升SQL编写质量,编写出更健壮、高效、安全的SQL数据库语句,让数据实实在在地为我们的业务加油助力。

一、基础语法与逻辑陷阱

序号
陷阱描述
核心原因
避坑方法
1
=
与IS NULL混用
三值逻辑(3VL):NULL = NULL→UNKNOWN,而非TRUE
判断NULL必须用IS NULL/IS NOT NULL
2
NOT IN
子查询包含NULL(单列)
col NOT IN (a, b, NULL)
≡col ≠ a AND col ≠ b AND col ≠ NULL→ 恒为UNKNOWN,返回空集
我们优先用NOT EXISTS;若用NOT IN,子查询必须加WHERE col IS NOT NULL。
我们注意:多列NOT IN(如:(c1,c2) NOT IN (...))行为与NOT EXISTS一致,不受NULL影响。
3
COUNT(列名)
与COUNT(*)混淆
COUNT(列名)
忽略NULL;COUNT(*)统计所有行(含全NULL行)
总记录数 →COUNT(*);非空值计数 →COUNT(col)
4
DISTINCT
与聚合函数的位置错误
COUNT(DISTINCT col1, col2)
在MySQL支持,但在PostgreSQL/SQL Server中需(col1, col2)或子查询
跨库兼容方案:用GROUP BY col1, col2+COUNT(*)
5
GROUP BY
与SELECT列不匹配
非聚合列未出现在GROUP BY中(尤其在关闭ONLY_FULL_GROUP_BY时)
启用ONLY_FULL_GROUP_BY模式,或显式聚合(如:ANY_VALUE(col))
6
HAVING
与WHERE混淆
WHERE
过滤行,HAVING过滤分组
行级条件 →WHERE;聚合条件 →HAVING
7
隐式转换导致索引失效
如:varchar_col = 123触发列转数字
保证类型一致
:varchar_col = '123'
8
OR
条件中的NULL逻辑错误
col = 1 OR NULL
→UNKNOWN,而非col = 1 OR col IS NULL
我们要显式写出所有条件,避免将NULL作为布尔表达式
9
自增ID依赖连续性
回滚、删除、批量插入失败等导致ID空洞
业务编号由应用层生成(如:UUID、雪花ID)
10
CASE
表达式类型不一致
不同分支返回类型不同 → 隐式转换或报错
所有分支统一类型(如:CAST(... AS VARCHAR))

统一原理提示:以上陷阱(1、2、8)均源于SQL的三值逻辑(TRUE/FALSE/UNKNOWN),NULL不等于任何值(包括自身),任何与NULL的比较结果均为UNKNOWN。

二、索引与性能优化

序号
陷阱描述
核心原因
避坑方法
11
索引列使用函数
函数破坏索引有序性,如:DATE(created_at) = '2024-01-01'
函数应用在常量或创建函数索引(如:MySQL的虚拟列索引)
12
联合索引顺序错误
未遵循"最左前缀原则"
将选择性高的列放前面;我们注意:查询条件只需覆盖索引前缀列即可生效(如:索引(a,b)对WHERE b=? AND a=?有效,但对WHERE b=?无效)
13
过度索引
索引过多导致写入性能下降、存储膨胀
只保留必要索引,我们要定期清理冗余索引
14
忽略覆盖索引
未利用包含查询所有列的索引
设计包含WHERE和SELECT列的联合索引
15
LIKE
前缀通配符
LIKE '%abc'
无法使用索引
避免前缀通配符,考虑全文索引或倒排索引
16
索引选择性低
对基数低的列(如:性别)建索引效果差
不建索引或与其他列组成联合索引
17
OR
连接不同索引列
WHERE a=1 OR b=2
无法有效利用索引
用UNION ALL拆分查询
18
ORDER BY
与索引方向不匹配
排序方向与索引顺序相反导致文件排序
创建与排序方向一致的索引(如:(a ASC, b DESC))
19
对大文本字段建索引
TEXT/BLOB
列建索引开销大,且通常只支持前缀索引
使用前缀索引(谨慎选择长度)或全文索引
20
FORCE INDEX
滥用
强制使用不合适的索引,忽视数据变化;降低可维护性
信任优化器
(在统计信息准确、查询结构清晰的前提下);仅在统计信息严重失真等临时场景下谨慎使用,并尽快通过更新统计信息或调整索引解决根本问题。
我们强烈建议:避免长期依赖FORCE INDEX,它会掩盖真正的性能问题。

三、查询与子查询

序号
陷阱描述
核心原因
避坑方法
21
SELECT *
滥用
读取不必要字段,增加I/O、网络开销和缓存污染
只查询需要的列
22
子查询嵌套过深
复杂子查询导致优化器无法有效优化
拆分为简单查询或使用JOIN/CTE
23
UNION
代替UNION ALL
不必要的去重排序开销
确定无重复时用UNION ALL
24
多表查询列名歧义
未用别名限定同名列,结果不可预测
始终用表别名限定列(如:u.name)
25
LIMIT
大偏移分页
LIMIT 100000, 10
需扫描大量行
基于游标分页(WHERE id > last_id ORDER BY id LIMIT 10)
26
CTE性能误解
多数数据库中CTE默认是语法糖(内联展开):
PostgreSQL≥12:支持显式MATERIALIZED;
SQL Server/MySQL:通常内联,仅优化器判断有利时可能物化;
递归CTE(WITH RECURSIVE)必须物化
复杂CTE多次引用时,手动物化到临时表更可控
27
子查询返回多行导致错误
WHERE col = (子查询)
子查询返回多行
用IN或保证子查询返回单行(如:加LIMIT 1)
28
EXISTS
与IN误用
性能取决于外层表大小 vs 子查询结果集大小及索引:
外层小 + 子查询大 →EXISTS(短路);
子查询结果集小(<1k行)→IN(哈希查找)
优先保证语义正确;性能敏感时,我们用EXPLAIN ANALYZE验证
29
关联子查询效率低
每行触发一次子查询(N+1问题)
转换为JOIN或LATERAL JOIN(PostgreSQL)/APPLY(SQL Server)
30
忽略EXPLAIN ANALYZE
依赖预估执行计划,不验证实际执行情况
复杂查询用EXPLAIN ANALYZE(或EXPLAIN (ANALYZE, BUFFERS))分析真实性能

四、数据修改与事务

序号
陷阱描述
核心原因
避坑方法
31
大事务
长时间持有锁,降低并发,增加回滚成本
拆分为小事务,移除非数据库操作(如:HTTP调用)
32
错误的事务隔离级别
一致性需求与隔离级别不匹配(如:幻读问题)
核心业务用REPEATABLE READ(MySQL)或SERIALIZABLE,一般查询用READ COMMITTED
33
TRUNCATE
在事务中使用
跨数据库行为不一致
:
MySQL/Oracle:DDL,隐式提交,不可回滚;
PostgreSQL/SQL Server:DML,可回滚
需要回滚时我们统一用DELETE;跨库开发避免依赖TRUNCATE事务行为
34
丢失更新
并发更新覆盖彼此结果
使用原子更新(SET col = col + 1)、悲观锁(SELECT ... FOR UPDATE)或乐观锁(版本号)
35
INSERT
未指定列名
依赖表结构顺序,变更后出错
始终显式指定列名
36
UPDATE
多表未限定列
未用别名指定更新列所属表,导致歧义
SET
子句中用表别名限定列(如:SET u.status = 1)
37
UPSERT
多唯一键冲突
同时违反多个唯一键时行为不可预测(如:MySQL可能更新错误行)
避免多唯一键表使用UPSERT,或明确指定冲突键(如:PostgreSQL的ON CONFLICT (col))
38
触发器中的复杂逻辑
触发器失败导致主事务回滚,且难以调试
触发器保持简单(如:审计日志),复杂逻辑放应用层
39
批量插入未优化
单条INSERT插入过多行
合理分批插入(如:每批1000行),使用LOAD DATA INFILE(MySQL)或COPY(PostgreSQL)
40
未处理死锁
忽略死锁异常,导致程序中断
实现死锁重试机制(如:最多3次),优化锁顺序(按固定顺序访问资源)

TRUNCATE跨数据库行为对比:

数据库
TRUNCATE
可回滚?
类型
MySQL
否
DDL(隐式提交)
PostgreSQL
是
DML
SQL Server
是
DML
Oracle
否
DDL(隐式提交)

五、表结构与设计

序号
陷阱描述
核心原因
避坑方法
41
ENUM
类型过度使用
频繁变更ENUM值需ALTER TABLE,且跨数据库兼容性差
用关联表替代可能变更的ENUM
42
外键级联操作滥用
ON DELETE CASCADE
导致大规模隐式删除,难以追踪
应用层控制关联数据删除,或仅在明确场景下使用级联
43
时区设置不一致
应用与数据库时区不同导致时间错乱
统一使用UTC存储,应用层处理时区转换
44
NOT NULL
滥用
为性能盲目添加NOT NULL,限制业务灵活性
根据业务逻辑决定是否允许NULL(如:“可选字段”应允许NULL)
45
大表不分区
单表过大(>千万行)导致查询缓慢
按时间或业务维度分区(如:RANGE、LIST)
46
CTAS 复制表结构不完整
CREATE TABLE ... AS SELECT通常不复制主键、外键、唯一约束、索引、默认值(DEFAULT)等元数据
;部分数据库(如:Oracle)可能保留NOT NULL约束,但行为不一致
绝不依赖CTAS复制元数据
;用CREATE TABLE ... LIKE复制结构,再INSERT INTO ... SELECT
47
字段类型选择不当
如:用VARCHAR(255)存储手机号(固定长度)
选择最合适的类型(如:CHAR(11)存手机号,TINYINT存状态)
48
缺少主键
无主键导致行标识困难,InnoDB会隐式生成6字节主键,影响性能
为所有表定义显式主键(自增、UUID、业务键)
49
自增主键滥用
分布式环境自增主键冲突,难以分库分表
分布式系统使用UUID(注意性能)或雪花算法(Snowflake)
50
表名/列名使用关键字
需用反引号(MySQL)或双引号(PostgreSQL)包裹,易出错
避免使用SQL关键字命名(如:order,group,user)

六、JOIN与关联操作

序号
陷阱描述
核心原因
避坑方法
51
JOIN
条件遗漏导致笛卡尔积
无连接条件,结果行数爆炸(N×M)
始终为JOIN加ON条件
52
不同字符集/校对规则列JOIN
需实时转换,无法用索引
统一字符集(如:utf8mb4)和校对规则(如:utf8mb4_unicode_ci)
53
过多表JOIN
超过6-8表JOIN性能下降明显,优化器复杂度指数增长
拆分查询,中间结果暂存到临时表
54
LEFT JOIN
后过滤右表
WHERE
条件过滤右表(如:WHERE b.col = 'x')导致LEFT JOIN变INNER JOIN
右表过滤条件放ON子句:
✗ LEFT JOIN b ON a.id = b.a_id WHERE b.col = 'x'
✓ LEFT JOIN b ON a.id = b.a_id AND b.col = 'x'
55
循环JOIN
表自关联条件错误(如:a.id = b.parent_id AND b.id = a.parent_id)导致无限循环
我们谨慎设计自关联条件,加深度限制(如:递归CTE中的MAXRECURSION)
56
大表JOIN小表顺序错误
驱动表选择不当影响性能(如:大表驱动小表)
小表作为驱动表,大表作为被驱动表(保证被驱动表关联列有索引)
57
NATURAL JOIN
隐式关联
依赖列名匹配,易因新增同名列导致错误
用显式JOIN ... ON替代
58
忽略JOIN索引
被驱动表关联列无索引,导致全表扫描
保证被驱动表关联列有索引
59
CROSS JOIN
误用
无条件交叉连接产生大量数据
保证有必要的过滤条件,或明确业务需要笛卡尔积
60
关联条件使用函数
关联列用函数(如:JOIN ... ON UPPER(a.name) = UPPER(b.name))导致索引失效
避免在关联条件中使用函数;或使用函数索引

七、JSON与特殊类型

序号
陷阱描述
核心原因
避坑方法
61
JSON字段低效查询
JSON_EXTRACT(col, '$.key')
导致索引失效
使用原生运算符(MySQL:col->>'$.key';PostgreSQL:col->>'key')
62
JSON索引使用不当
未按数据库要求创建和使用JSON索引(如:MySQL需虚拟列,PostgreSQL需GIN)
遵循数据库JSON索引最佳实践
63
过度使用JSON类型
适合非结构化数据,不适合频繁查询/过滤的字段
结构化数据用普通列,非结构化用JSON
64
对JSON数组查询低效
数组元素查询需遍历,性能差
考虑拆分为关联表(规范化)
65
时区存储在JSON中
时区处理复杂,易导致时间错误
重要时间字段用TIMESTAMP/DATETIME类型,单独存储时区信息

八、架构与运维

序号
陷阱描述
核心原因
避坑方法
66
在线DDL风险
大表ALTER TABLE导致长时间锁表(尤其MySQL 5.6以前)
使用pt-online-schema-change(MySQL)或pg_repack(PostgreSQL)等工具
67
不设置sql_mode
宽松模式允许不规范操作(如:插入超长字符串被截断),埋下隐患
开启严格模式(STRICT_TRANS_TABLES, ONLY_FULL_GROUP_BY, NO_ZERO_DATE等)
68
忽略慢查询日志
无法发现性能瓶颈SQL
开启慢查询日志(long_query_time=1),定期用pt-query-digest分析
69
SELECT ... INTO OUTFILE
导出
服务器端创建文件,有安全风险(需FILE权限,路径受限)
客户端工具导出(如:mysql -e "SELECT ..." > file.csv)
70
视图安全隐患
默认SQL SECURITY DEFINER导致权限提升(调用者获得定义者权限)
创建视图时我们显式指定SQL SECURITY INVOKER
71
未定期优化表
碎片积累导致性能下降(尤其MyISAM或频繁DELETE的InnoDB)
定期执行OPTIMIZE TABLE(MySQL)或VACUUM FULL(PostgreSQL)
72
统计信息过时
导致优化器选择错误执行计划
定期更新统计信息(ANALYZE TABLE)
73
连接池配置不当
连接数过多(OOM)或过少(排队)影响性能
根据服务器配置(CPU、内存)和QPS调整连接池大小
74
未备份或备份无效
数据丢失风险
定期备份(全量+增量),并验证备份有效性(恢复演练)
75
大表COUNT(*)
InnoDB的COUNT(*)在无覆盖索引时需扫描聚簇索引(因MVCC无法使用持久化行数),性能较差
维护计数表(通过触发器或应用层更新)或使用缓存(如:Redis)

九、安全与权限

序号
陷阱描述
核心原因
避坑方法
76
过度授权
应用账号拥有过高权限(如:DROP,GRANT)
遵循最小权限原则(仅SELECT,INSERT,UPDATE,DELETE)
77
明文存储密码
数据库泄露导致密码暴露
存储密码哈希(如:bcrypt、scrypt),绝不存明文或可逆加密
78
SQL注入风险
拼接SQL字符串,未用参数化查询
始终使用参数化查询
(Prepared Statement)或ORM的安全接口
79
敏感数据未加密
敏感信息(身份证、银行卡)明文存储
加密敏感字段(应用层加密或TDE),并控制密钥管理
80
未审计数据库操作
安全事件无法追溯
开启审计日志(如:MySQL Enterprise Audit, pgAudit),记录关键操作(DDL、DML on sensitive tables)

十、高级特性与行业惯例

序号
陷阱描述
核心原因
避坑方法
81
分区表使用不当
分区键选择错误(如:不用查询条件中的列)导致全分区扫描
按高频查询条件选择分区键(如:created_at)
82
存储过程过度复杂
难以调试、测试、版本控制和跨数据库迁移
复杂逻辑放应用层,存储过程仅用在简单封装或批量操作
83
外键滥用影响性能
频繁写入时外键检查开销大(尤其无索引的外键)
高并发OLTP场景可在应用层维护外键逻辑;若用外键,保证被引用列有索引
84
全文索引使用不当
不适合短文本(<3字符)或高频更新场景(重建开销大)
考虑Elasticsearch、Meilisearch等专门搜索引擎
85
未使用读写分离
单库压力过大(尤其读多写少场景)
读操作分流到从库(注意主从延迟问题)
86
忽略数据库版本差异
不同版本功能和行为有差异(如:GROUP BY默认行为、JSON支持)
开发、测试、生产环境,我们要使用相同版本,并查阅官方文档


阅读原文:原文链接


该文章在 2025/9/28 10:31:55 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-1  粤公网安备44030602007207号