作为一名经历过多次数据库性能调优的老手,我深知SQL优化对系统性能的决定性影响。记得去年我们一个核心业务系统突然出现响应迟缓,经过排查发现是一条简单的订单查询SQL在数据量增长后执行时间从50ms飙升到5秒。这次经历让我深刻认识到:SQL优化不是锦上添花,而是系统稳定运行的必备技能。
数据库操作通常会成为系统性能瓶颈,原因有三:
通过优化SQL,我们能够:
提示:在生产环境中,一条未优化的SQL可能导致整个系统雪崩。我曾见过一个未加索引的COUNT(*)查询拖垮了整个MySQL实例。
新手常犯的错误就是使用SELECT *,这会导致:
sql复制-- 反例:查询所有字段
SELECT * FROM users WHERE id = 100;
-- 正例:只查询必要字段
SELECT username, email FROM users WHERE id = 100;
优化原理:
实战技巧:
sql复制-- 反例:对price字段做运算
SELECT * FROM products WHERE price * 0.8 > 100;
-- 正例:运算移到右侧
SELECT * FROM products WHERE price > 100 / 0.8;
原理分析:
数据库索引就像书本目录,是按照字段原始值排序的。当你对字段做运算时,相当于改变了"排序规则",数据库无法直接使用索引查找。
sql复制-- 假设phone是varchar类型
-- 反例:数字与字符串比较
SELECT * FROM users WHERE phone = 13800138000;
-- 正例:类型一致
SELECT * FROM users WHERE phone = '13800138000';
问题排查:
典型的高性能分页方案:
sql复制-- 反例:传统分页
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;
-- 正例:基于游标的分页
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 20;
性能对比:
| 方案 | 扫描行数 | 执行时间 |
|---|---|---|
| LIMIT OFFSET | 100020 | 450ms |
| WHERE id> | 20 | 2ms |
深度优化:
对于复杂分页,可以考虑:
sql复制-- 反例:大表驱动小表
SELECT o.* FROM large_orders o
JOIN small_users u ON o.user_id = u.id
WHERE u.type = 'VIP';
-- 正例:小表驱动大表
SELECT o.* FROM (SELECT id FROM small_users WHERE type = 'VIP') u
JOIN large_orders o ON u.id = o.user_id;
执行过程对比:
反例执行流程:
正例执行流程:
sql复制-- 必须为关联字段创建索引
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- 多字段关联时考虑联合索引
CREATE INDEX idx_orders_composite ON orders(user_id, status);
索引选择策略:
sql复制-- 场景:查询有订单的用户
-- 方案1:IN (最差)
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
-- 方案2:EXISTS (中等)
SELECT * FROM users u WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- 方案3:JOIN (最佳)
SELECT DISTINCT u.* FROM users u
JOIN orders o ON u.id = o.user_id;
性能实测数据(100万用户,500万订单):
| 方案 | 执行时间 | 扫描行数 |
|---|---|---|
| IN | 12.5s | 6M |
| EXISTS | 8.2s | 1.5M |
| JOIN | 3.7s | 1.1M |
分批删除示例:
sql复制DELIMITER //
CREATE PROCEDURE batch_delete()
BEGIN
DECLARE affected_rows INT DEFAULT 1;
WHILE affected_rows > 0 DO
DELETE FROM logs WHERE created_at < '2023-01-01' LIMIT 1000;
SET affected_rows = ROW_COUNT();
DO SLEEP(0.1); -- 控制删除频率
END WHILE;
END //
DELIMITER ;
批量插入优化:
sql复制-- 单条插入 (不推荐)
INSERT INTO products(name) VALUES ('A');
INSERT INTO products(name) VALUES ('B');
-- 批量插入 (推荐)
INSERT INTO products(name) VALUES
('A'),('B'),('C'),('D'),('E');
-- 使用LOAD DATA INFILE (极快)
LOAD DATA INFILE '/tmp/products.csv'
INTO TABLE products
FIELDS TERMINATED BY ',';
创建统计表:
sql复制CREATE TABLE daily_stats (
stat_date DATE PRIMARY KEY,
user_count INT,
order_count INT,
revenue DECIMAL(12,2),
INDEX (stat_date)
);
定时更新任务:
sql复制-- 每天凌晨执行
INSERT INTO daily_stats
SELECT
CURRENT_DATE() - INTERVAL 1 DAY,
COUNT(DISTINCT user_id),
COUNT(*),
SUM(amount)
FROM orders
WHERE DATE(created_at) = CURRENT_DATE() - INTERVAL 1 DAY
ON DUPLICATE KEY UPDATE
user_count = VALUES(user_count),
order_count = VALUES(order_count),
revenue = VALUES(revenue);
EXPLAIN关键字段:
| 字段 | 说明 | 优化目标 |
|---|---|---|
| type | 访问类型 | 至少达到range,理想是ref或const |
| key | 使用的索引 | 确保使用了合适的索引 |
| rows | 预估扫描行数 | 尽可能减少 |
| Extra | 额外信息 | 避免Using filesort, Using temporary |
典型问题诊断:
误区1:索引越多越好
误区2:所有查询都能用索引
误区3:联合索引顺序无关紧要
适当反范式化:
字段类型选择:
分区表策略:
关键监控指标:
优化闭环流程:
在实际工作中,我习惯将这套检查清单应用到每个新上线的SQL语句评审中。曾经有一个报表查询经过这样的检查后,执行时间从45秒降到了0.8秒,效果立竿见影。记住,数据库优化不是一次性的工作,而是需要持续关注和改进的过程。