1. 索引失效的典型场景与原理剖析
MySQL索引失效是DBA和开发人员日常工作中最常见的高频问题之一。当查询性能突然下降时,我们首先怀疑的就是索引是否有效发挥作用。根据我多年处理生产环境性能问题的经验,索引失效通常发生在以下七种典型场景中:
1.1 隐式类型转换导致的索引失效
当查询条件中的数据类型与索引列定义的数据类型不一致时,MySQL会进行隐式类型转换,这将导致索引失效。例如:
sql复制-- 表结构
CREATE TABLE users (
id INT PRIMARY KEY,
phone VARCHAR(20) NOT NULL,
INDEX idx_phone (phone)
);
-- 失效查询(数字与字符串比较)
EXPLAIN SELECT * FROM users WHERE phone = 13800138000;
在这个案例中,虽然phone字段上有索引,但因为查询条件中的13800138000是数字类型,而phone字段是VARCHAR类型,MySQL会进行隐式类型转换,导致无法使用索引。正确的做法是保持类型一致:
sql复制-- 有效查询
EXPLAIN SELECT * FROM users WHERE phone = '13800138000';
经验提示:在金融系统中处理交易记录时,我曾遇到一个典型案例。account_no字段定义为VARCHAR但查询时使用了数值,导致日均百万级的交易查询从50ms飙升到3s。通过EXPLAIN发现类型转换问题后,修改查询条件使类型匹配,性能立即恢复正常。
1.2 前导模糊查询导致索引失效
LIKE查询以通配符开头时(如'%keyword'),索引通常无法被使用:
sql复制-- 表结构
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
content TEXT,
INDEX idx_title (title)
);
-- 索引失效的查询
EXPLAIN SELECT * FROM articles WHERE title LIKE '%MySQL%';
这是因为B+树索引的结构决定了它只能从最左开始匹配。对于这种需求,可以考虑以下解决方案:
- 使用全文索引(FULLTEXT)替代普通索引
- 调整业务逻辑,尽量使用后置通配符(如'MySQL%')
- 引入专门的搜索引擎如Elasticsearch
1.3 对索引列使用函数或运算
当在索引列上使用函数或运算时,索引将失效:
sql复制-- 表结构
CREATE TABLE orders (
id INT PRIMARY KEY,
order_date DATETIME NOT NULL,
amount DECIMAL(10,2),
INDEX idx_order_date (order_date)
);
-- 索引失效的查询
EXPLAIN SELECT * FROM orders WHERE DATE(order_date) = '2023-01-01';
EXPLAIN SELECT * FROM orders WHERE amount + 100 > 500;
解决方案包括:
- 重写查询避免在索引列上使用函数
- 使用函数索引(MySQL 8.0+支持)
- 新增计算列并建立索引
1.4 OR条件使用不当
OR条件可能导致索引失效,特别是当OR连接的条件中有列没有索引时:
sql复制-- 表结构
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category_id INT,
price DECIMAL(10,2),
INDEX idx_category (category_id)
);
-- 索引可能失效的查询
EXPLAIN SELECT * FROM products WHERE category_id = 5 OR price > 100;
这种情况下,优化器可能选择全表扫描而非使用category_id上的索引。解决方案包括:
- 为price字段也添加索引
- 使用UNION ALL改写查询
- 考虑使用WHERE...IN替代部分OR条件
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 复合索引的最左前缀原则
复合索引的使用遵循最左前缀原则,这是索引失效中最复杂的场景之一。我曾在电商系统中处理过一个典型案例:商品表有复合索引(category_id, status, create_time),但很多查询却无法使用索引。
2.1 最左前缀原则详解
复合索引就像电话簿,先按姓氏排序,同姓氏再按名字排序。如果不知道姓氏,仅知道名字是无法快速查找的。例如:
sql复制-- 表结构
CREATE TABLE products (
id INT PRIMARY KEY,
category_id INT NOT NULL,
status TINYINT NOT NULL,
create_time DATETIME NOT NULL,
INDEX idx_comp (category_id, status, create_time)
);
-- 有效使用索引的查询
EXPLAIN SELECT * FROM products
WHERE category_id = 3 AND status = 1 AND create_time > '2023-01-01';
-- 部分使用索引(只用到category_id)
EXPLAIN SELECT * FROM products WHERE category_id = 3;
-- 索引失效的查询(跳过最左列)
EXPLAIN SELECT * FROM products WHERE status = 1 AND create_time > '2023-01-01';
2.2 范围查询对复合索引的影响
范围查询会使其后的索引列失效:
sql复制-- 使用到category_id和status(create_time失效)
EXPLAIN SELECT * FROM products
WHERE category_id = 3 AND status > 0 AND create_time > '2023-01-01';
这种情况下,建议调整索引顺序或拆分查询条件。
3. 索引选择性不足导致失效
索引选择性是指索引列中不同值的数量与表中记录总数的比例。选择性低的索引(如性别、状态等)可能被优化器忽略:
sql复制-- 表结构
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
gender ENUM('M','F') NOT NULL,
INDEX idx_gender (gender)
);
-- 可能不使用索引的查询
EXPLAIN SELECT * FROM employees WHERE gender = 'M';
当表中数据量很大且gender分布均匀时(约50%M,50%F),使用索引可能不如全表扫描高效。解决方案包括:
- 避免为低选择性列单独建索引
- 将低选择性列作为复合索引的后缀
- 使用FORCE INDEX强制使用索引(需谨慎)
4. 优化器选择不使用索引
MySQL优化器会根据统计信息决定是否使用索引。以下情况优化器可能选择全表扫描:
- 表中数据量很少(通常小于表总行数的30%)
- 查询需要返回大部分数据(如超过20-30%的行)
- 索引统计信息过期
可以通过ANALYZE TABLE更新统计信息,或使用FORCE INDEX提示(生产环境慎用):
sql复制-- 强制使用索引
EXPLAIN SELECT * FROM products FORCE INDEX(idx_category) WHERE category_id = 5;
5. 索引失效的诊断与排查
当怀疑索引失效时,应按以下步骤排查:
5.1 使用EXPLAIN分析
sql复制EXPLAIN SELECT * FROM table WHERE condition;
关注以下关键字段:
- type:ALL表示全表扫描
- possible_keys:可能使用的索引
- key:实际使用的索引
- rows:预估扫描行数
5.2 使用性能模式监控
sql复制-- 开启性能监控
SET GLOBAL performance_schema = ON;
-- 查看未使用索引的查询
SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL AND COUNT_STAR = 0;
5.3 使用慢查询日志
sql复制-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- 查看日志中未使用索引的查询
# mysqldumpslow -s t -t 10 -g 'full scan' /var/log/mysql/mysql-slow.log
6. 索引设计与使用的最佳实践
根据多年经验,我总结了以下索引使用原则:
- 为高频查询条件创建索引,但避免过度索引
- 复合索引顺序遵循:等值查询列在前,范围查询列在后
- 避免在索引列上使用函数或计算
- 定期使用ANALYZE TABLE更新统计信息
- 使用覆盖索引减少回表操作
- 对于长字符串考虑使用前缀索引
- 监控索引使用情况,删除无用索引
7. 真实案例:电商系统索引优化
我曾处理过一个电商平台的性能问题:商品搜索响应时间从200ms逐渐增加到2s以上。通过分析发现:
- 原索引:
INDEX idx_search (category_id, status) - 主要查询:
WHERE category_id IN (...) AND status=1 AND title LIKE '%手机%'
优化方案:
- 新增复合索引:
INDEX idx_search_v2 (category_id, status, title(20)) - 修改查询为:
WHERE category_id IN (...) AND status=1 AND title LIKE '手机%' - 对完全模糊搜索引入Elasticsearch
优化后,查询性能恢复至300ms以内,QPS从50提升到200+。
