1. 为什么SQL优化是每个开发者的必修课记得三年前我接手过一个电商平台的订单查询模块当时用户抱怨查个订单要等半分钟。排查发现有个同事写的SQL竟然嵌套了5层子查询单次执行需要扫描上百万行数据。经过重写优化后响应时间直接降到200毫秒以内——这就是SQL优化的魔力。好的SQL就像精心设计的公路系统烂SQL像没有红绿灯的十字路口所有车辆(数据)挤作一团优秀SQL像立体交通枢纽不同车辆(数据)各行其道互不干扰本文将分享我十年数据库实战中总结的快且美的SQL编写心法涵盖索引设计、查询重构、执行计划解读等核心技巧。无论你是要解决现有性能瓶颈还是想预防未来可能的问题这些方法都能让你少走弯路。2. 索引设计的艺术与科学2.1 索引选择的三维评估法我常用三维度评估法选择索引区分度字段唯一值比例如性别字段区分度差热力值该字段在WHERE中的出现频率组合效应多字段索引的协同效果-- 反面教材无区分度的索引 CREATE INDEX idx_gender ON users(gender); -- 优秀实践高区分度组合索引 CREATE INDEX idx_phone_company ON users(phone_prefix, company_id);重要提示索引不是越多越好。每新增一个索引写操作会多一次维护开销。我的经验值是OLTP系统单表索引不超过5个。2.2 最容易被忽略的索引失效场景这些索引杀手你肯定遇到过隐式类型转换WHERE user_id 1001user_id是整型函数操作WHERE DATE(create_time) 2023-01-01前导通配符WHERE content LIKE %关键字%解决方案-- 改为范围查询 WHERE create_time BETWEEN 2023-01-01 00:00:00 AND 2023-01-01 23:59:59 -- 使用全文索引替代LIKE ALTER TABLE articles ADD FULLTEXT INDEX ft_content(content);3. 查询重构的五大黄金法则3.1 从链式查询到批处理典型反模式# 循环中执行SQL产生N1问题 for user_id in user_list: cursor.execute(SELECT * FROM orders WHERE user_id %s, [user_id])优化方案-- 单次查询获取所有数据 SELECT * FROM orders WHERE user_id IN (1, 2, 3, ...) ORDER BY FIELD(user_id, 1, 2, 3,...)实测对比数据量原始方式批处理方式100条1.2s0.05s1000条12.8s0.15s3.2 JOIN操作的性能陷阱与突破常见误区-- 笛卡尔积风险 SELECT * FROM A, B WHERE A.x B.y优化方案-- 显式JOIN 索引提示 SELECT /* INDEX(B idx_y) */ A.*, B.info FROM A INNER JOIN B ON A.x B.y WHERE A.status 1JOIN性能对照表JOIN类型适合场景危险信号INNER JOIN关联强约束数据大表连大表LEFT JOIN保留主表全部记录右表无索引STRAIGHT_JOIN强制指定驱动表错误选择驱动表4. 执行计划深度解析实战4.1 EXPLAIN关键指标解读执行计划示例------------------------------------------------------------------------------------------------------------ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ------------------------------------------------------------------------------------------------------------ | 1 | SIMPLE | users | NULL | ref | idx_phone | idx_phone | 66 | const | 1 | 100.00 | Using index | ------------------------------------------------------------------------------------------------------------核心指标解析type列从优到劣 system const eq_ref ref range index ALLrows列估算扫描行数超过1000就要警惕Extra列出现Using filesort或Using temporary需要优化4.2 强制索引的使用艺术有时优化器会选错索引-- 强制使用特定索引 SELECT * FROM orders FORCE INDEX(idx_user_status) WHERE user_id 1001 AND status 2;使用场景判断流程先用EXPLAIN查看优化器选择对比强制不同索引的执行计划用SQL_NO_CACHE测试真实执行时间确认效果后考虑使用FORCE INDEX5. 高级优化技巧与实战案例5.1 分页查询的终极优化方案典型低效分页SELECT * FROM articles ORDER BY create_time DESC LIMIT 10000, 20; -- 需要先扫描10020行优化方案-- 方案1延迟关联 SELECT a.* FROM articles a INNER JOIN ( SELECT id FROM articles ORDER BY create_time DESC LIMIT 10000, 20 ) AS tmp ON a.id tmp.id; -- 方案2游标分页适合无限滚动 SELECT * FROM articles WHERE create_time 2023-06-01 00:00:00 ORDER BY create_time DESC LIMIT 20;5.2 大数据量下的统计优化常规COUNT性能问题SELECT COUNT(*) FROM logs WHERE status 1; -- 全表扫描优化方案-- 方案1使用汇总表 CREATE TABLE stats_daily ( day DATE PRIMARY KEY, active_count INT, error_count INT ); -- 方案2近似统计误差1% EXPLAIN SELECT TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME logs;6. 避坑指南与性能监控6.1 开发者常犯的七个错误**过度使用SELECT ***问题传输不必要的数据修复只查询需要的列在索引列上运算-- 错误示范 WHERE YEAR(create_time) 2023 -- 正确写法 WHERE create_time BETWEEN 2023-01-01 AND 2023-12-31忽略事务隔离级别场景高并发下的幻读问题方案合理使用SELECT FOR UPDATE6.2 性能监控体系搭建我的监控方案慢查询日志捕获执行超过1秒的SQL# my.cnf配置 slow_query_log 1 slow_query_log_file /var/log/mysql-slow.log long_query_time 1性能看板指标QPS/TPS波动连接数使用率缓存命中率定期健康检查-- 检查索引利用率 SELECT index_name, rows_read FROM sys.schema_index_statistics WHERE table_schema your_db;最后分享一个真实案例某金融系统通过优化一个核心SQL将对账作业从4小时缩短到15分钟。关键是把多个子查询改写成JOIN并增加了复合索引。这再次证明好的SQL既是科学也是艺术。
阅读完成 · 觉得有帮助?