1. MySQL索引的本质与价值
第一次接触MySQL索引时,我曾天真地以为它就是个简单的"目录"。直到某次线上查询超时,才真正理解索引对数据库性能的决定性影响。索引本质上是一种特殊的数据结构,它通过预排序和快速定位机制,将随机I/O转化为顺序I/O,使查询效率提升几个数量级。
在典型的用户订单系统中,无索引的SELECT * FROM orders WHERE user_id=100可能触发全表扫描,而良好的索引能让相同查询在毫秒级完成。这种差异在数据量超过百万行时会变得极其明显——前者可能导致整个数据库连接池被占满,后者则能轻松应对高并发。
关键认知:索引不是银弹。它用额外的存储空间和写入开销,换取查询性能的提升。这种权衡需要根据业务场景精心设计。
2. 索引类型深度解析
2.1 B+树索引:MySQL的默认选择
B+树是MySQL最常用的索引结构,相比二叉树,它具有两大核心优势:
- 矮胖树形结构:3-4层就能存储千万级数据,减少磁盘I/O次数
- 叶子节点链表:支持高效的范围查询(如
WHERE id BETWEEN 100 AND 200)
创建示例:
sql复制-- 单列索引
CREATE INDEX idx_username ON users(username);
-- 复合索引
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
2.2 哈希索引:精准匹配的利器
MEMORY引擎默认使用哈希索引,其特性包括:
- O(1)时间复杂度查找
- 仅支持等值比较(=, IN)
- 不支持排序和范围查询
sql复制-- 创建自定义哈希索引(基于CRC32)
ALTER TABLE products ADD COLUMN url_crc INT UNSIGNED;
UPDATE products SET url_crc = CRC32(product_url);
CREATE INDEX idx_url_crc ON products(url_crc);
2.3 全文索引:文本搜索的解决方案
对于大文本字段的模糊匹配,LIKE '%keyword%'会导致全表扫描。全文索引通过倒排索引实现高效搜索:
sql复制-- 创建全文索引
ALTER TABLE articles ADD FULLTEXT INDEX ft_content (content);
-- 使用MATCH AGAINST查询
SELECT * FROM articles
WHERE MATCH(content) AGAINST('数据库优化' IN NATURAL LANGUAGE MODE);
2.4 空间索引:地理数据处理
GIS应用中使用R-Tree结构处理空间数据:
sql复制CREATE TABLE locations (
id INT PRIMARY KEY,
name VARCHAR(100),
position POINT NOT NULL SRID 4326,
SPATIAL INDEX(position)
);
3. 索引优化实战策略
3.1 最左前缀原则
复合索引(A,B,C)实际相当于建立了:
- (A)
- (A,B)
- (A,B,C)
三个索引。以下查询能利用该索引:
sql复制WHERE A=1 AND B=2
WHERE A=1 ORDER BY B
WHERE A=1 AND B>5 AND C=10 -- C条件只能部分使用
3.2 索引选择性优化
选择性=不重复值/总行数,高选择性列更适合索引:
sql复制-- 计算gender列的选择性(通常较差)
SELECT COUNT(DISTINCT gender)/COUNT(*) FROM users;
-- 优化方案:组合低选择性列
ALTER TABLE logs ADD INDEX idx_type_status (log_type, status);
3.3 覆盖索引技巧
当索引包含所有查询字段时,可避免回表操作:
sql复制-- 原始查询(需要回表)
EXPLAIN SELECT user_name FROM users WHERE age=25;
-- 优化为覆盖索引
ALTER TABLE users ADD INDEX idx_age_name (age, user_name);
4. 索引失效的八大陷阱
-
隐式类型转换:
sql复制-- user_id是varchar类型时 SELECT * FROM users WHERE user_id=100; -- 失效 -
前导通配符:
sql复制SELECT * FROM products WHERE name LIKE '%手机%'; -- 全表扫描 -
索引列运算:
sql复制SELECT * FROM orders WHERE YEAR(create_time)=2023; -- 失效 -
OR条件不当:
sql复制-- 只有user_id有索引时 SELECT * FROM orders WHERE user_id=1 OR amount>100; -- 全表扫描 -
NOT条件:
sql复制SELECT * FROM products WHERE status NOT IN (1,2); -- 可能失效 -
函数操作:
sql复制SELECT * FROM users WHERE SUBSTRING(username,1,3)='abc'; -
JOIN字段类型不匹配:
sql复制-- orders.user_id是INT,users.id是VARCHAR SELECT * FROM orders JOIN users ON orders.user_id=users.id; -
优化器放弃索引:
sql复制-- 当数据量很小时,MySQL可能选择全表扫描 SELECT * FROM small_table WHERE indexed_column=1;
5. 面试高频问题剖析
5.1 为什么用B+树不用B树?
- B+树非叶子节点不存数据,使得一个页能容纳更多键值
- 叶子节点形成链表,范围查询效率更高
- 数据全在叶子节点,查询稳定性更好
5.2 聚簇索引 vs 非聚簇索引
| 特性 | 聚簇索引 | 非聚簇索引 |
|---|---|---|
| 存储内容 | 数据行 | 主键值 |
| 数量限制 | 每表1个 | 多个 |
| 访问方式 | 直接获取数据 | 需要回表 |
| 插入速度 | 依赖插入顺序 | 相对更快 |
5.3 如何排查索引问题?
- 使用EXPLAIN分析执行计划
- 检查
possible_keysvskey - 关注
type列(最好到ref级别) - 观察
rows预估扫描行数 - 检查
Extra列是否出现"Using filesort"
sql复制EXPLAIN FORMAT=JSON
SELECT * FROM orders WHERE user_id=100 AND status='paid'\G
6. 生产环境索引管理
6.1 在线索引操作
MySQL 5.6+支持在线DDL:
sql复制-- 不锁表的添加索引(INPLACE算法)
ALTER TABLE orders ADD INDEX idx_amount (amount), ALGORITHM=INPLACE, LOCK=NONE;
-- 查看进度
SELECT * FROM performance_schema.events_stages_current;
6.2 索引监控与维护
sql复制-- 查看未使用索引
SELECT * FROM sys.schema_unused_indexes;
-- 重建碎片化索引
ALTER TABLE orders ENGINE=InnoDB;
-- 监控索引统计信息
ANALYZE TABLE orders PERSISTENT FOR ALL;
6.3 分库分表下的索引策略
- 全局表使用相同索引结构
- 分片键必须包含在唯一索引中
- 避免跨分片索引查询
- 考虑使用分布式ID避免冲突
7. 高级索引技巧
7.1 索引跳跃扫描
MySQL 8.0+特性,即使不符合最左前缀也能利用复合索引:
sql复制-- 复合索引 (gender, age)
SELECT * FROM users WHERE age>20; -- 8.0+可能利用索引
7.2 降序索引优化
sql复制-- 创建降序索引
CREATE INDEX idx_created_desc ON orders(created_at DESC);
-- 适合混合排序场景
SELECT * FROM orders
ORDER BY created_at DESC, amount ASC;
7.3 函数索引
MySQL 8.0+支持:
sql复制-- 创建函数索引
CREATE INDEX idx_name_lower ON users((LOWER(username)));
-- 查询使用
SELECT * FROM users WHERE LOWER(username)='admin';
8. 真实案例:电商系统索引优化
某电商平台商品表原始结构:
sql复制CREATE TABLE products (
id BIGINT PRIMARY KEY,
title VARCHAR(200),
category_id INT,
price DECIMAL(10,2),
seller_id INT,
created_at DATETIME,
modified_at DATETIME,
status TINYINT,
INDEX idx_category (category_id)
);
优化方案:
- 为高频查询
WHERE seller_id=? AND status=?添加复合索引 - 价格区间查询添加
(category_id, price)索引 - 后台管理系统添加
(status, created_at)索引 - 商品标题搜索改用全文索引
最终DDL:
sql复制ALTER TABLE products
ADD INDEX idx_seller_status (seller_id, status),
ADD INDEX idx_cate_price (category_id, price),
ADD INDEX idx_status_created (status, created_at),
ADD FULLTEXT INDEX ft_title (title);
优化后效果:
- 商品列表查询从1200ms降至80ms
- 商家后台加载速度提升5倍
- 搜索接口TP99从800ms降到150ms