去年双十一大促期间,我们的电商平台遭遇了严重的性能瓶颈。监控系统显示,订单查询接口的平均响应时间达到了惊人的5.2秒,高峰期甚至出现超时情况。作为核心数据库负责人,我带领团队通过系统性的SQL优化,最终将查询耗时稳定控制在0.5秒以内。这个案例让我深刻认识到:在高并发场景下,SQL质量直接决定系统生死。
EXPLAIN命令是DBA的"X光机"。记得第一次分析那个5秒查询时,执行计划显示type=ALL(全表扫描),扫描行数rows=120万。这就是性能灾难的根源——数据库引擎被迫检查每一行记录。
关键指标解读:
优化案例:
sql复制-- 原始查询(5.2秒)
EXPLAIN SELECT * FROM orders WHERE user_id=1001 AND status='paid';
-- 优化后(0.3秒)
EXPLAIN SELECT order_id, amount FROM orders
WHERE user_id=1001 AND status='paid'
ORDER BY create_time DESC;
通过创建(user_id, status, create_time)复合索引,type从ALL提升到ref,Extra中的Using filesort也消失了。
我们曾有一个shop_orders表,包含(shop_id, order_no)联合索引。但开发同学写的查询是:
sql复制SELECT * FROM shop_orders WHERE order_no='EB123456' -- 索引失效
这是因为违反了最左前缀原则。解决方案有两种:
某次优化中发现一个"诡异"的索引失效案例:
sql复制SELECT * FROM users WHERE mobile=13800138000 -- 索引失效
原因在于mobile字段是varchar类型,而查询使用数字比较导致隐式转换。修正为:
sql复制SELECT * FROM users WHERE mobile='13800138000' -- 索引生效
对于长文本字段,前缀索引能显著节省空间。我们通过这个SQL计算最佳长度:
sql复制SELECT
COUNT(DISTINCT LEFT(product_name,10))/COUNT(*) AS selectivity10,
COUNT(DISTINCT LEFT(product_name,15))/COUNT(*) AS selectivity15
FROM products;
当selectivity15达到0.95时,创建INDEX idx_name(product_name(15))即可兼顾性能与存储。
传统分页在大数据量时性能急剧下降:
sql复制SELECT * FROM orders ORDER BY id LIMIT 100000,20 -- 需要扫描100020行
优化方案:记住上次查询的最大ID
sql复制SELECT * FROM orders
WHERE id > (SELECT id FROM orders ORDER BY id LIMIT 100000,1)
ORDER BY id LIMIT 20;
实测从2.1秒降到0.05秒,效果惊人。
错误案例:
sql复制SELECT o.*, u.name
FROM orders o JOIN users u ON o.user_id=u.id -- 以orders为驱动表
WHERE u.vip_level=3
优化关键:
优化后执行计划显示驱动表变为users,type=ref,性能提升4倍。
问题查询:
sql复制SELECT * FROM products
WHERE category_id IN (
SELECT id FROM categories WHERE is_hot=1
)
执行计划显示DEPENDENT SUBQUERY,效率极低。
优化方案:
sql复制SELECT p.*
FROM products p JOIN categories c ON p.category_id=c.id
WHERE c.is_hot=1;
查询时间从1.8秒降至0.2秒。
我们的服务器有32G内存,初始配置:
ini复制innodb_buffer_pool_size=8G
innodb_log_file_size=200M
优化后配置:
ini复制innodb_buffer_pool_size=24G # 物理内存的75%
innodb_log_file_size=2G # 保证1小时写入量
监控命令:
sql复制SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
-- Innodb_buffer_pool_read_requests: 总读取量
-- Innodb_buffer_pool_reads: 从磁盘读取的次数
命中率 = 1 - (reads/requests),优化后从92%提升到99.7%。
配置my.cnf:
ini复制slow_query_log=1
slow_query_log_file=/var/log/mysql/mysql-slow.log
long_query_time=0.2 # 记录超过200ms的查询
log_queries_not_using_indexes=1
使用pt-query-digest分析:
bash复制pt-query-digest /var/log/mysql/mysql-slow.log > slow_report.txt
优化前后对比指标:
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| QPS | 1200 | 8000 | 566% |
| 平均响应时间 | 420ms | 65ms | 84%↓ |
| CPU使用率 | 85% | 35% | 58%↓ |
这次优化经历让我明白,SQL优化不是一次性工作,而是需要建立持续监控-分析-优化的闭环体系。特别是在业务快速迭代过程中,要建立SQL审核机制,防止性能退化。最后分享一个心得:有时候,最好的优化不是技术方案,而是和产品经理讨论是否可以调整需求——这往往能带来意想不到的性能提升。