目录
- 前言
- 一、什么是数据库索引?
- 1.1 索引的基本概念
- 1.2 索引解决了什么问题?
- 1.3 索引的优点与代价
- 二、数据库索引的底层原理
- 2.1 为什么不能总是全表扫描?
- 2.2 常见索引数据结构
- 1. 哈希索引(Hash Index)
- 2. 二叉搜索树(BST)
- 3. AVL 树与红黑树
- 4. B 树与 B+ 树
- 2.3 为什么 MySQL 选择 B+ 树?
- B+ 树的查询复杂度
- 2.4 InnoDB 的两种重要索引:聚簇索引和二级索引
- 1. 聚簇索引(Clustered Index)
- 2. 二级索引(Secondary Index)
- 2.5 什么是覆盖索引?
- 三、数据库索引的主要类型
- 3.1 按功能划分
- 3.2 主键索引
- 3.3 普通索引
- 3.4 唯一索引
- 3.5 联合索引
- 四、如何创建、查看和删除索引?
- 4.1 创建索引的四种常见方式
- 方式一:创建表时定义主键和索引
- 方式二:创建普通索引
- 方式三:创建唯一索引
- 方式四:创建联合索引
- 4.2 查看表中有哪些索引
- 4.3 删除索引
- 4.4 如何判断索引是否有必要?
- 五、联合索引最重要的规则:最左前缀原则
- 5.1 什么是最左前缀原则?
- 5.2 哪些 SQL 能利用这个联合索引?
- 5.3 为什么联合索引的字段顺序如此重要?
- 5.4 联合索引字段顺序应该怎么安排?
- 5.5 范围查询对联合索引有什么影响?
- 六、哪些 SQL 写法可能导致索引无法有效使用?
- 6.1 在索引字段上使用函数
- 6.2 对索引字段进行计算
- 6.3 使用 `LIKE '%关键字%'`
- 6.4 隐式类型转换
- 6.5 `OR` 条件
前言
数据库索引是 Java 后端开发中非常重要的知识点。无论你使用 MySQL、MyBatis-Plus,还是在 Spring Boot 项目中编写复杂的查询 SQL,理解索引都能帮助你解决慢 SQL、分页查询性能差、联合索引失效等实际问题。
这篇文章以 MySQL 8.0 + InnoDB 存储引擎 为主要讲解对象,结合电商订单、企业用车订单等业务场景,从基础概念讲到 B+ 树底层原理,再到索引设计、SQL 编写规范和性能排查。
关于本文,你可以学到:
- 什么是数据库索引?为什么它能够提高查询速度?
- MySQL 为什么主要使用 B+ 树,而不是普通二叉树、哈希表?
- 聚簇索引、二级索引、联合索引、覆盖索引分别是什么?
- 为什么联合索引有最左前缀原则?
- 为什么对索引字段使用函数,可能导致索引失效?
- 如何正确创建索引?索引越多越好吗?
- 如何使用
EXPLAIN分析 SQL 是否使用了索引? - 在 Java 后端项目中,应该遵守哪些 SQL 索引规范?
一、什么是数据库索引?
1.1 索引的基本概念
数据库索引(Index)是一种帮助数据库快速定位数据的数据结构。
我们可以把它理解成一本书的目录。
书籍目录
通过章节名称和页码,快速找到需要的内容。
数据库索引
通过索引键值和数据位置,快速定位数据库记录。
假设我们有一张用户表user,里面存储了 100 万个用户。
CREATE TABLE user ( id BIGINT PRIMARY KEY, username VARCHAR(100), age INT, email VARCHAR(200) );现在需要查询用户名为zhangsan的用户:
SELECT * FROM user WHERE username = 'zhangsan';如果username没有索引,数据库可能需要从大量记录中逐行检查用户名是否匹配。
如果username上存在合适的索引,数据库就有机会通过索引快速找到匹配记录,而不需要检查整张表。
创建索引:
CREATE INDEX idx_user_username ON user (username);这样,数据库就拥有了一份按照username组织的索引结构。
注意:创建索引不意味着每条 SQL 都一定使用索引。 优化器会根据查询条件、数据分布、索引选择性和预估成本,决定采用哪种执行计划。
1.2 索引解决了什么问题?
数据库查询通常涉及两个过程:
- 找到符合条件的数据。
- 读取这些数据的完整记录,或者返回所需字段。
索引主要优化第一个过程,也可能通过覆盖索引同时避免第二个过程。
例如:
SELECT * FROM user WHERE id = 10001;如果id是主键,InnoDB 可以利用主键索引直接定位记录。
再看:
SELECT * FROM user WHERE age = 25;如果age没有索引,数据库可能需要扫描整张表;如果有合适的索引,就可以通过索引查找符合条件的记录。
但并不是所有场景下索引都更快。例如,查询 80% 的数据时,使用索引后仍然需要大量回表,优化器可能认为全表扫描更划算。
1.3 索引的优点与代价
| 优点 | 说明 |
|---|---|
| 提高查询效率 | 减少需要扫描的数据 |
| 加速排序 | 合适的索引可能避免额外排序 |
| 加速分组 | 某些GROUP BY可以利用索引顺序 |
| 支持唯一约束 | 唯一索引可以防止重复键值 |
索引也有成本:
- 占用额外的磁盘空间和 Buffer Pool 内存。
- 插入数据时需要维护索引。
- 更新索引字段时,可能需要调整索引结构。
- 删除数据时,也需要维护对应索引。
- 索引设计不合理会增加优化器选择和维护成本。
因此,索引优化的目标不是“给所有字段都创建索引”,而是用合理的索引支撑高频、重要的查询,同时控制写入和存储成本。
二、数据库索引的底层原理
理解索引为什么快,关键在于理解数据是如何组织的。
2.1 为什么不能总是全表扫描?
假设有 100 万条用户记录,需要查询id = 900000。
如果数据库没有可用索引,它可能需要检查大量记录才能确认结果。
在理想化的逐条检查模型下,最坏情况下需要检查 100 万条记录。
如果表按照主键建立了合适的 B+ 树索引,查找过程则类似于不断缩小搜索范围。
这两种方式的差别可以简单理解为:
真实查询的耗时还取决于磁盘 I/O、缓存命中、回表次数、结果集大小和并发负载,并不只取决于比较次数。
2.2 常见索引数据结构
1. 哈希索引(Hash Index)
哈希索引通过哈希函数,把键值映射到相应的数据位置。
例如:
user_id = 1001 → 位置 A user_id = 1002 → 位置 B user_id = 1003 → 位置 C理想情况下,哈希索引可以非常高效地处理等值查找:
SELECT * FROM user WHERE id = 1001;但它存在一些局限:
- 不适合普通的范围查找,例如
id BETWEEN 1000 AND 2000。 - 不适合依靠索引顺序完成排序。
- 哈希冲突需要额外处理。
- 不支持 B+ 树那样的有序范围遍历。
MySQL InnoDB 的常规表索引主要使用 B+ 树。InnoDB 还支持自适应哈希索引,但它是存储引擎内部根据访问模式建立的优化机制,不等于用户可以任意创建的普通哈希索引。
2. 二叉搜索树(BST)
二叉搜索树的特点是:
- 左子树的键值小于节点。
- 右子树的键值大于节点。
如果树比较平衡,查询效率可以达到 \(O(\log N)\)。
但是,如果插入的数据长期有序,普通二叉搜索树可能退化成链表,查询效率接近 \(O(N)\)。
3. AVL 树与红黑树
AVL 树和红黑树通过平衡机制,避免普通二叉搜索树严重退化。
不过,对于存储在磁盘上的大型数据库索引,它们通常不是最理想的主索引结构。
原因在于:二叉树每个节点通常只有少量分支,查找深层数据可能需要访问更多节点。而磁盘访问的代价远高于内存中的简单比较。
数据库希望一个节点能够容纳很多键值,尽量减少树的高度。
4. B 树与 B+ 树
B 树和 B+ 树都是多路平衡搜索树,适合磁盘或分页存储环境。
它们的关键优势是:一个节点可以包含多个键值和多个子节点,从而让树保持较低的高度。
InnoDB 的主要索引结构是 B+ 树。
2.3 为什么 MySQL 选择 B+ 树?
B+ 树是理解 InnoDB 索引的核心。
它有以下几个重要特点:
- 多路分支:一个节点可以存储多个键值,拥有多个子节点。
- 树高较低:相比二叉树,通常可以用更少的层数定位数据。
- 叶子节点有序:叶子节点中的键值按顺序组织,适合范围查询。
- 叶子节点之间通常通过双向链表连接:方便顺序遍历。
- InnoDB 聚簇索引的叶子节点存储完整的行数据,而二级索引的叶子节点存储索引键和对应的主键值。
例如,一棵简化的 B+ 树可能如下:
假设我们执行:
SELECT * FROM user WHERE id = 90;数据库会从根节点开始,根据键值决定向哪个子节点查找,最终到达包含90的叶子节点。
如果查询的是:
SELECT * FROM user WHERE id BETWEEN 50 AND 120;数据库可以先定位范围起点,再沿叶子节点的有序链表向后读取符合条件的记录。
这就是 B+ 树既适合等值查询,也适合范围查询的重要原因。
B+ 树的查询复杂度
在理想化的平衡树模型中,定位某个键值通常可以近似理解为 \(O(\log N)\)。
但要注意:这并不意味着每条查询只需要一次磁盘读取。实际访问次数还取决于树高、节点是否在缓存中、数据是否需要回表等因素。
2.4 InnoDB 的两种重要索引:聚簇索引和二级索引
这是整个索引体系中最重要的知识之一。
1. 聚簇索引(Clustered Index)
InnoDB 的聚簇索引决定了表记录的主要组织方式。
当表有主键时,通常使用主键作为聚簇索引。
例如:
CREATE TABLE user ( id BIGINT PRIMARY KEY, username VARCHAR(100), age INT, email VARCHAR(200) ) ENGINE = InnoDB;主键索引的叶子节点存储完整的用户记录。
可以将其简化理解为:
主键 B+ 树 | v +--------------------------------+ | id=1001, username=张三, age=25 | +--------------------------------+ | id=1002, username=李四, age=30 | +--------------------------------+ | id=1003, username=王五, age=28 | +--------------------------------+以上是逻辑示意,不是实际物理页的存储格式。
因此:
SELECT * FROM user WHERE id = 1002;可以通过主键索引直接找到包含完整记录的叶子节点,不需要再通过另一个索引定位这条记录。
如果没有显式主键,InnoDB 会选择合适的非空唯一索引作为聚簇索引;如果也没有合适的唯一索引,则会创建内部使用的隐藏聚簇索引。
2. 二级索引(Secondary Index)
除了聚簇索引,表还可以建立其他索引。
例如:
CREATE INDEX idx_user_username ON user (username);这个索引的叶子节点主要保存:
username的索引键值。- 对应记录的主键值
id。
例如:
username 二级索引 李四 → id=1002 王五 → id=1003 张三 → id=1001当执行:
SELECT * FROM user WHERE username = '李四';大致执行过程如下:
- 先通过
idx_user_username找到username = '李四'的索引记录。 - 从二级索引记录中获取主键值
id = 1002。 - 再通过主键索引查找完整的用户记录。
第二步之后再次访问主键索引的过程,通常称为回表。
所以,二级索引可以帮助快速定位记录,但查询不在二级索引中的其他字段时,可能需要回表。
2.5 什么是覆盖索引?
覆盖索引不是一种独立的索引类型,而是一种查询访问方式。
如果一个查询需要的所有字段,都能直接从某个索引中获取,那么这个索引就覆盖了该查询。
假设存在:
CREATE INDEX idx_user_username_age ON user (username, age);执行:
SELECT username, age FROM user WHERE username = '李四';因为查询条件需要username,返回字段需要username和age,而这几个字段都在索引中,因此可以直接读取索引完成查询,不需要再访问聚簇索引读取完整记录。
这就是覆盖索引。
在执行计划中,经常可以看到Extra列出现:
Using index它通常表示查询能够通过索引覆盖所需数据。
对比下面两个查询:
-- 可能使用覆盖索引 SELECT username, age FROM user WHERE username = '李四'; -- 通常需要回表获取其他字段 SELECT * FROM user WHERE username = '李四';覆盖索引有助于减少随机访问和回表开销,尤其适合高频列表查询。
不过,不应该为了让所有查询都覆盖而把大量字段塞进索引。过宽的索引会增加磁盘占用和写入成本。
image
MySQL开发者专区
+1
三、数据库索引的主要类型
3.1 按功能划分
| 索引类型 | 作用 | 典型场景 |
|---|---|---|
| 主键索引 | 唯一标识记录,InnoDB 中通常是聚簇索引 | id |
| 普通索引 | 提高指定字段的查找效率 | username |
| 唯一索引 | 限制索引键值不能重复 | email |
| 联合索引 | 同时对多个字段建立索引 | company_id, status |
| 全文索引 | 支持特定类型的全文检索 | 文章内容搜索 |
需要注意,以上分类并不完全互斥。例如,主键索引本身也具有唯一性,联合索引也可以是唯一索引。
3.2 主键索引
CREATE TABLE orders ( id BIGINT PRIMARY KEY, order_no VARCHAR(64), amount DECIMAL(12, 2) );主键必须唯一且不能为NULL。
主键索引不仅用于查找,还决定了 InnoDB 聚簇索引的组织方式。
因此,主键设计通常应该:
- 保证唯一性。
- 尽可能稳定,避免频繁更新。
- 尽量简洁,避免不必要地使用很宽的主键。
- 在大量插入场景下,评估键值的插入模式对页分裂和写入性能的影响。
自增整数是常见的选择,但并非所有业务都必须使用自增主键。
3.3 普通索引
CREATE INDEX idx_orders_order_no ON orders (order_no);普通索引不要求索引键值唯一。
例如,多个订单可能具有相同的某个业务分类字段,因此普通索引适合这类查询。
SELECT * FROM orders WHERE order_no = 'ORD20261015001';3.4 唯一索引
CREATE UNIQUE INDEX uk_orders_order_no ON orders (order_no);唯一索引除了能够加速查询,还能保证非NULL的索引键组合不重复。
例如,业务要求订单号不能重复,就应该通过数据库唯一约束保证,而不是只在 Java 代码中先查询一次再插入。
因为“先查后插”在并发场景下仍可能产生竞态条件。
注意:MySQL 的唯一索引允许存在多个NULL值,具体行为还需要结合字段定义与业务约束理解。
3.5 联合索引
联合索引也叫复合索引,是对多个字段共同建立的索引。
例如:
CREATE INDEX idx_orders_company_status_time ON orders (company_id, status, create_time);它的索引顺序是:
company_id → status → create_time这个顺序非常重要。
联合索引并不是给三个字段分别创建三个独立索引,而是将多个字段按指定顺序组合成索引键。
它特别适合固定业务查询模式,例如:
SELECT id, order_no, amount FROM orders WHERE company_id = 1001 AND status = 1 ORDER BY create_time DESC;该 SQL 的过滤和排序条件与联合索引较为匹配,但实际执行效果仍需要通过EXPLAIN验证。
联合索引的设计原则,将在后文详细说明。
四、如何创建、查看和删除索引?
4.1 创建索引的四种常见方式
方式一:创建表时定义主键和索引
CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(64) NOT NULL, company_id BIGINT NOT NULL, status TINYINT NOT NULL, amount DECIMAL(12, 2) NOT NULL, create_time DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_orders_order_no (order_no), KEY idx_orders_company_status_time ( company_id, status, create_time ) ) ENGINE = InnoDB;这是新建表时最清晰的方式之一。
方式二:创建普通索引
CREATE INDEX idx_orders_status ON orders (status);也可以使用:
ALTER TABLE orders ADD INDEX idx_orders_status (status);两种方式都可以用于给现有表添加普通索引。
方式三:创建唯一索引
CREATE UNIQUE INDEX uk_user_email ON user (email);执行之前要先确认现有数据是否违反唯一性要求,否则创建操作可能失败。
方式四:创建联合索引
CREATE INDEX idx_orders_company_status_time ON orders (company_id, status, create_time);字段的先后顺序决定了索引的组织顺序,不能随意调换。
MySQL 支持多种索引定义方式,具体语法可参考官方文档。
image
MySQL开发者专区
4.2 查看表中有哪些索引
SHOW INDEX FROM orders;或者:
SHOW CREATE TABLE orders;其中,SHOW INDEX可以查看索引名称、唯一性、索引字段顺序等信息。
重点关注:
Key_name:索引名称。Non_unique:是否允许重复。Seq_in_index:字段在联合索引中的顺序。Column_name:索引包含的字段。Cardinality:索引统计信息中的基数估计值。
4.3 删除索引
DROP INDEX idx_orders_status ON orders;也可以通过ALTER TABLE:
ALTER TABLE orders DROP INDEX idx_orders_status;删除索引之前,要确认没有其他重要 SQL 依赖它带来的性能收益。
生产环境还要考虑索引 DDL 对锁、I/O、复制延迟和业务流量的影响。大表建索引不应该不经评估就直接在高峰期执行。
4.4 如何判断索引是否有必要?
不要只根据“这个字段出现在WHERE中”就创建索引。
应该综合考虑:
- 查询是否高频。
- 表的数据量是否足够大。
- 条件能过滤掉多少记录。
- 是否存在可复用的联合索引。
- 查询是否需要排序、分页或关联其他表。
- 索引会带来多少写入和存储成本。
例如,status只有 0、1 两个值。如果某个值占了绝大多数记录,那么单独对status建索引未必划算。
但如果它和公司编号、创建时间组合成联合索引,仍可能非常有价值。
关键是看具体查询模式和数据分布,而不是孤立判断某个字段。
五、联合索引最重要的规则:最左前缀原则
这部分是面试和实际开发中最容易遇到的问题。
5.1 什么是最左前缀原则?
假设我们建立联合索引:
CREATE INDEX idx_orders_company_status_time ON orders (company_id, status, create_time);可以把它理解为索引键按照以下顺序组织:
第一层排序依据:company_id 第二层排序依据:status 第三层排序依据:create_time例如,索引中有以下键值:
| company_id | status | create_time |
|---|---|---|
| 1001 | 0 | 2026-10-01 09:00:00 |
| 1001 | 0 | 2026-10-02 10:00:00 |
| 1001 | 1 | 2026-10-01 08:00:00 |
| 1001 | 1 | 2026-10-03 12:00:00 |
| 1002 | 0 | 2026-10-01 09:00:00 |
注意:上表只是展示联合索引的排序逻辑,实际物理存储还有其他内部信息。
由于首先按照company_id排序,所以同一个公司的数据聚集在一起;公司相同的情况下,再按照status排序;前两个字段相同时,最后才按照create_time排序。
因此,这个索引天然适合从最左边的字段开始定位。
5.2 哪些 SQL 能利用这个联合索引?
查询一:WHERE company_id = 1001
(匹配最左前缀) 可以利用联合索引的第一列定位数据。
查询二:WHERE company_id = 1001 AND status = 1
(匹配最左前缀) 可以利用前两列缩小查找范围。
查询三:WHERE company_id = 1001 AND status = 1 AND create_time >= ‘2026-10-01’
(匹配最左前缀) 三个字段均有条件,可以利用三个索引列进行定位或范围扫描。
查询四:WHERE status = 1
(缺少最左列条件) 通常无法利用该联合索引直接定位 status = 1 的连续范围。不过,在特定条件下,MySQL 可能采用 Skip Scan 等优化;也可能扫描整个索引。因此不能简单断言绝对不使用索引。
查询五:WHERE company_id = 1001 AND create_time >= ‘2026-10-01’
(中间列缺失) 通常可以利用第一列 company_id 定位范围,但跳过了 status,后面的 create_time 通常不能像完整连续前缀那样直接用于构造精确的索引范围,可能需要额外过滤。
最左前缀原则的核心是:
联合索引按照字段顺序组织数据,越靠左的字段越决定索引的定位能力。
官方文档也说明了多列索引的键值是按照定义顺序组合起来的。
5.3 为什么联合索引的字段顺序如此重要?
看两个索引:
CREATE INDEX idx_a_b_c ON orders (a, b, c); CREATE INDEX idx_b_a_c ON orders (b, a, c);它们并不等价。
前者优先按照a排序,后者优先按照b排序。
如果系统中最常见的查询是:
WHERE a = ? AND b = ?那么两种索引都可能支持该查询,但如果还存在:
WHERE a = ?第一个索引通常更加合适。
而对于:
WHERE b = ?第二个索引通常更有优势。
设计联合索引时,要根据实际查询组合,而不是仅凭字段名称决定顺序。
5.4 联合索引字段顺序应该怎么安排?
实际设计中,可以综合考虑以下因素:
- 查询条件是否经常出现:高频查询优先考虑。
- 等值条件与范围条件:通常优先安排稳定出现的等值条件,再考虑范围条件。
- 过滤能力:区分度高的字段往往更有利于缩小范围,但不是唯一标准。
- 排序与分组需求:考虑
ORDER BY、GROUP BY的字段和方向。 - 索引复用能力:能覆盖多个重要查询的索引通常更有价值。
例如:
WHERE company_id = ? AND status = ? AND create_time >= ? AND create_time < ? ORDER BY create_time DESC可以考虑:
CREATE INDEX idx_orders_company_status_time ON orders (company_id, status, create_time);这里company_id、status是等值条件,create_time是范围条件,同时还用于排序。
这是一个合理的候选索引,但最终仍应通过真实数据与执行计划验证。
5.5 范围查询对联合索引有什么影响?
考虑以下 SQL:
SELECT * FROM orders WHERE company_id = 1001 AND status > 1 AND create_time = '2026-10-15 10:00:00';如果索引是:
(company_id, status, create_time)通常可以先根据company_id定位,再根据status构造范围扫描。
由于status已经是范围条件,后面的create_time通常不能继续作为同样意义上的连续索引范围边界。
但这不代表create_time完全没有作用:它仍可能参与索引条件下推、过滤,或者在特定执行计划中发挥作用。
不要把“范围条件之后的列通常不能继续缩小连续扫描区间”误解成“范围条件之后的所有索引列完全失效”。
六、哪些 SQL 写法可能导致索引无法有效使用?
在 Java 后端项目中,很多慢 SQL 并不是因为没有索引,而是因为 SQL 写法没有充分利用已有索引。
下面结合常见场景逐一说明。
6.1 在索引字段上使用函数
假设:
CREATE INDEX idx_orders_create_time ON orders (create_time);错误或不理想的写法:
SELECT * FROM orders WHERE DATE(create_time) = '2026-10-15';原因是:
DATE(create_time)对索引字段应用了函数,传统的普通索引通常不能直接按照原始create_time值构造高效的日期范围。
更推荐改写为左闭右开区间:
SELECT * FROM orders WHERE create_time >= '2026-10-15 00:00:00' AND create_time < '2026-10-16 00:00:00';这种写法更有利于利用create_time索引进行范围扫描。
在 Java 中,可以使用LocalDate计算当天起始时间和次日零点,再传入 SQL 参数。
如果确实需要频繁按日期表达式查询,MySQL 8.0 也支持函数索引,但需要针对具体表达式和版本设计,不应该默认用函数索引替代简单的范围改写。
6.2 对索引字段进行计算
例如:
SELECT * FROM orders WHERE amount + 100 > 500;假设amount上有普通索引,这种写法可能让索引范围构造变得困难。
可以在逻辑等价的情况下改为:
SELECT * FROM orders WHERE amount > 400;这里不仅 SQL 更容易利用索引,业务含义也更加直接。
但需要注意数据类型、精度和NULL语义,确保改写前后确实等价。
6.3 使用LIKE '%关键字%'
假设:
CREATE INDEX idx_user_username ON user (username);下面这种前缀匹配通常可以利用 B+ 树索引:
SELECT * FROM user WHERE username LIKE 'zhang%';而这种包含匹配:
SELECT * FROM user WHERE username LIKE '%zhang%';普通 B+ 树索引通常无法直接定位包含zhang的连续键值范围,可能需要扫描大量记录。
需要全文检索时,可以根据语言、分词、搜索语义和版本考虑FULLTEXT索引或专门的搜索引擎。
不过,MySQL 的全文索引不等同于任意子串匹配,不能简单地认为它能直接替代%关键词%。
6.4 隐式类型转换
假设:
CREATE TABLE user ( id BIGINT PRIMARY KEY, phone VARCHAR(20) ); CREATE INDEX idx_user_phone ON user (phone);如果phone存储的是字符串手机号,推荐:
SELECT * FROM user WHERE phone = '13800138000';而不是:
SELECT * FROM user WHERE phone = 13800138000;第二种写法会引入类型转换问题,可能影响索引使用,还可能改变比较语义。
需要特别注意的是:MySQL 的隐式转换是否影响索引,取决于具体字段类型、比较操作和执行计划,并不是所有类型不一致都会导致索引失效。
规范做法:参数类型尽量与数据库字段类型保持一致。
对于 Java 项目:
BIGINT通常对应Long。INT通常对应Integer。VARCHAR通常对应String。DATETIME应使用明确的日期时间类型和统一的时区约定。
6.5OR条件
假设user表分别有idx_user_name和idx_user_phone。
SELECT * FROM user WHERE username = 'zhangsan' OR phone = '13800138000';这条 SQL 并不一定无法使用索引。
如果两个分支都适合使用索引,MySQL 有机会采用index_merge等执行方式。
但如果其中一个分支缺少索引,或者两个条件的选择性不理想,查询成本可能很高。
因此,不要把“出现 OR 就一定失效”当成规则。
正确做法是:
- 确认两个分支的查询条件。
- 检查相关字段是否有合适索引。
- 使用
EXPLAIN查看执行计划。 - 必要时评估改写方案,但不能盲目将
OR拆成UNION,因为去重语义、排序和执行成本都可能