一、当我们的数据库开始"喘粗气"
最近遇到个有意思的案例:某电商平台的商品搜索功能,在促销期间响应时间从200ms暴涨到8秒。DBA团队排查后发现,原本运行良好的索引突然"罢工",导致全表扫描近百万条数据。这就像你家的指纹锁突然失灵,每次开门都要试遍所有钥匙,怎能不让人抓狂?
二、索引失效的七种典型场景
场景1:联合索引的"左右为难"
-- 创建联合索引
ALTER TABLE orders ADD INDEX idx_status_amount (order_status, total_amount);
-- 失效查询(跳过了status字段)
SELECT * FROM orders WHERE total_amount > 1000;
解析:就像查字典不按首字母直接翻页,当查询条件未包含联合索引的最左列时,索引就会失效。优化方案是调整查询条件或重建索引顺序。
场景2:类型转换的"隐形杀手"
-- 创建索引
ALTER TABLE users ADD INDEX idx_phone (phone);
-- phone字段是varchar类型
SELECT * FROM users WHERE phone = 13800138000; -- 数字查询导致失效
解析:字段类型不匹配就像用人民币支付美元账单,数据库必须做隐式转换才能比较。解决方法是在应用层确保类型一致,或使用显式转换函数。
场景3:函数操作的"魔法失效"
-- 创建日期索引
ALTER TABLE logs ADD INDEX idx_create_time (create_time);
-- 使用日期函数导致失效
SELECT * FROM logs WHERE DATE_FORMAT(create_time, '%Y-%m') = '2023-08';
解析:对索引列使用函数就像把书页撕下来重排,破坏了原有结构。可优化为范围查询:
SELECT * FROM logs
WHERE create_time >= '2023-08-01'
AND create_time < '2023-09-01';
场景4:OR条件的"双重陷阱"
-- 单个字段索引
ALTER TABLE products ADD INDEX idx_price (price);
-- OR查询导致全表扫描
SELECT * FROM products
WHERE price > 1000 OR stock < 10;
解析:OR条件就像同时要求厨师做川菜和粤菜,必须两边都准备好。解决方案是拆分为UNION查询:
SELECT * FROM products WHERE price > 1000
UNION
SELECT * FROM products WHERE stock < 10;
场景5:模糊查询的"精确谜题"
-- 普通索引
ALTER TABLE articles ADD INDEX idx_title (title);
-- 前导通配符查询
SELECT * FROM articles WHERE title LIKE '%数据库%';
解析:前导通配符就像蒙着眼睛找人,必须逐个排查。建议使用全文索引:
ALTER TABLE articles ADD FULLTEXT idx_fulltext_title (title);
SELECT * FROM articles
WHERE MATCH(title) AGAINST('+数据库' IN BOOLEAN MODE);
场景6:范围查询的"多米诺效应"
-- 联合索引
ALTER TABLE sales ADD INDEX idx_date_region (sale_date, region);
-- 范围查询阻断后续索引
SELECT * FROM sales
WHERE sale_date BETWEEN '2023-01-01' AND '2023-12-31'
AND region = '华东';
解析:范围查询就像推倒第一张骨牌,后续索引列无法继续使用。优化方案是调整索引顺序或使用覆盖索引。
场景7:统计误差的"信任危机"
-- 检查索引健康状况
SHOW INDEX FROM orders;
-- 当Cardinality值接近表记录数时
ALTER TABLE orders ANALYZE TABLE;
解析:统计信息过期就像用旧地图找新路,优化器可能做出错误判断。定期执行ANALYZE TABLE更新统计信息。
三、索引重建的三种武器
方法1:ALTER TABLE重建
-- 最彻底的索引重建方式
ALTER TABLE orders DROP INDEX idx_old;
ALTER TABLE orders ADD INDEX idx_new (new_columns);
优点:完整重建索引结构
缺点:锁表时间长(实测5GB表需要3分钟)
方法2:OPTIMIZE TABLE优化
-- 同时优化表和索引
OPTIMIZE TABLE orders;
优点:自动处理碎片和统计信息
缺点:需要MyISAM或InnoDB存储引擎支持
方法3:在线重建工具
-- 使用pt-online-schema-change
pt-online-schema-change --alter "DROP KEY idx_old, ADD KEY idx_new(col)" D=db,t=orders
优点:几乎不影响线上业务
缺点:需要安装Percona工具包
四、慢查询分析的黄金搭档:EXPLAIN
实战解读执行计划
EXPLAIN SELECT * FROM orders
WHERE order_date > '2023-01-01'
AND customer_id = 12345;
关键字段解读:
- type:ALL表示全表扫描,range表示范围扫描
- key:实际使用的索引名称
- rows:预估扫描行数
- Extra:Using where表示需要回表查询
五、技术方案的权衡之道
索引优化的双面性
优势:
- 查询速度提升10-100倍
- 减少服务器资源消耗
- 提升并发处理能力
局限:
- 增加写操作耗时(测试显示写入速度降低15%)
- 需要持续维护成本
- 可能引入新的死锁问题
六、操作时的保命守则
- 备份先行:重大变更前务必执行mysqldump
- 灰度发布:先在从库执行变更,观察效果
- 监控三件套:QPS、慢查询日志、锁等待情况
- 避免高峰期:选择业务低谷时段操作(推荐凌晨2-4点)
- 版本验证:不同MySQL版本对索引策略有差异(实测5.7与8.0存在20%性能差异)
七、真实案例的启示
某社交平台的消息表在优化前:
- 数据量:1.2亿条
- 慢查询占比:38%
- 平均响应时间:2.3秒
通过索引重建和优化后:
- 有效索引使用率提升至92%
- 慢查询占比降至3%
- 平均响应时间缩短到180ms
- 服务器CPU负载从70%降至35%
八、总结与展望
索引优化就像给数据库做定期体检,需要持续监测和及时干预。随着MySQL 8.0新增的不可见索引、降序索引等特性,我们有了更多优化手段。但核心原则始终不变:理解业务场景,掌握数据特征,用合适的工具解决具体问题。