LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

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

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

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

一、基础语法与逻辑陷阱

序号
陷阱描述
核心原因
避坑方法
1
=
IS NULL混用
三值逻辑(3VL):NULL = NULLUNKNOWN,而非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(列名)
忽略NULLCOUNT(*)统计所有行(含全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
忽略覆盖索引
未利用包含查询所有列的索引
设计包含WHERESELECT列的联合索引
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问题)
转换为JOINLATERAL 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)
始终为JOINON条件
52
不同字符集/校对规则列JOIN
需实时转换,无法用索引
统一字符集(如:utf8mb4)和校对规则(如:utf8mb4_unicode_ci)
53
过多表JOIN
超过6-8表JOIN性能下降明显,优化器复杂度指数增长
拆分查询,中间结果暂存到临时表
54
LEFT JOIN
后过滤右表
WHERE
条件过滤右表(如:WHERE b.col = 'x')导致LEFT JOININNER 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-2025 ClickSun All Rights Reserved