去年双十一期间,我接手了一个电商平台的数据库优化项目。当时最令人头疼的是订单查询接口,高峰期响应时间经常突破3秒,用户投诉不断。经过两周的索引优化,最终将查询时间稳定在0.2秒左右。这个案例让我深刻认识到:合理的索引设计对数据库性能的影响,往往比硬件升级更立竿见影。
InnoDB的主键索引采用B+树结构,这种数据结构有三大特点:
以用户表为例:
sql复制CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
) ENGINE=InnoDB;
当执行SELECT * FROM users WHERE id=123时,数据库只需进行2-3次磁盘IO就能定位到数据。我曾测试过1000万条数据的查询,耗时仅0.01秒。
关键经验:自增主键能避免页分裂,但如果是UUID等随机值做主键,插入性能会下降30%以上
普通索引(也叫二级索引)的结构很特别:
sql复制CREATE INDEX idx_email ON users(email);
这个索引的B+树叶子节点存储的不是完整数据,而是主键值。当执行SELECT * FROM users WHERE email='user@example.com'时,需要先查idx_email索引找到主键,再回表查主键索引获取完整数据。
实测发现,回表操作会使查询性能降低40%左右。解决方案是使用覆盖索引:
sql复制SELECT id, email FROM users WHERE email='user@example.com';
这样只需扫描idx_email索引就能获取所需数据。
电商订单表的典型查询场景:
sql复制SELECT * FROM orders
WHERE user_id=1001 AND status='paid'
ORDER BY create_time DESC;
最优索引设计应该是:
sql复制CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);
这里有个重要规律:索引的第一列(user_id)必须出现在WHERE条件中,否则索引会失效。比如下面这个查询就用不上索引:
sql复制SELECT * FROM orders WHERE status='paid';
设计联合索引时,字段顺序应该遵循:
我曾做过对比测试:
最常见的错误:
sql复制-- phone是varchar类型
SELECT * FROM users WHERE phone=13800138000; -- 索引失效
SELECT * FROM users WHERE phone='13800138000'; -- 使用索引
这个问题在慢查询日志中很难发现,需要仔细检查执行计划。
日期查询的典型错误:
sql复制SELECT * FROM orders WHERE YEAR(create_time)=2023; -- 全表扫描
优化方案:
sql复制SELECT * FROM orders
WHERE create_time >= '2023-01-01'
AND create_time < '2024-01-01';
优化后查询速度提升20倍。
传统分页在大数据量时性能极差:
sql复制SELECT * FROM orders LIMIT 100000, 20; -- 慢
优化方案(延迟关联):
sql复制SELECT * FROM orders o
JOIN (
SELECT id FROM orders
ORDER BY create_time
LIMIT 100000, 20
) tmp ON o.id=tmp.id;
实测从2秒降到0.1秒。
覆盖索引是指索引包含所有需要查询的字段:
sql复制CREATE INDEX idx_cover ON orders(order_id, user_id, amount);
-- 使用覆盖索引
SELECT order_id, user_id FROM orders
WHERE amount > 1000;
Extra列显示"Using index",性能提升50%。
配置方法:
ini复制slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
分析工具pt-query-digest的用法:
bash复制pt-query-digest /var/log/mysql/mysql-slow.log > slow_report.txt
定期检查索引健康度:
sql复制ANALYZE TABLE orders; -- 更新统计信息
当索引碎片率超过30%时:
sql复制OPTIMIZE TABLE orders; -- 重建表
建议在业务低峰期执行。
有一次在凌晨3点处理生产事故,发现一个看似无害的索引变更导致QPS下降50%。教训是:任何索引变更都要先在测试环境验证。