1642 字
8 分钟
数据库查询优化实战:从慢查询到高性能
为什么查询优化如此重要?
一条慢查询就能拖垮整个系统。
想象一下:
- 用户等待页面加载 → 查询慢 → 用户流失
- API 请求超时 → 查询慢 → 服务降级
- 定时任务卡住 → 查询慢 → 数据积压
查询优化不是可选的,是必须的。
慢查询分析
1. 开启慢查询日志
MySQL
-- 查看慢查询配置SHOW VARIABLES LIKE 'slow_query%';
-- 开启慢查询SET GLOBAL slow_query_log = 'ON';SET GLOBAL long_query_time = 1; -- 超过 1 秒记录PostgreSQL
-- 配置 postgresql.conflog_min_duration_statement = 1000 -- 超过 1 秒记录2. 分析慢查询日志
# MySQLmysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# PostgreSQLpgBadger /var/log/postgresql/postgresql.log3. 使用 EXPLAIN
EXPLAIN SELECT * FROM users WHERE id = 1;关键指标:
- type:访问类型(ALL, index, range, ref, eq_ref, const)
- key:使用的索引
- rows:预估扫描行数
- Extra:额外信息(Using filesort, Using temporary)
索引优化
索引类型
B-Tree 索引
CREATE INDEX idx_email ON users(email);- 最常用的索引类型
- 支持 =, >, <, BETWEEN, LIKE ‘abc%’
- 不支持 LIKE ‘%abc’
哈希索引
CREATE INDEX idx_hash USING HASH ON users(username);- 只支持等值查询
- 查询速度极快
- MySQL Memory 引擎支持
全文索引
CREATE FULLTEXT INDEX idx_content ON articles(content);- 用于文本搜索
- 支持 MATCH AGAINST
复合索引
-- 单列索引CREATE INDEX idx_name ON users(name);CREATE INDEX idx_age ON users(age);
-- 复合索引(更好)CREATE INDEX idx_name_age ON users(name, age);最左前缀原则:
WHERE name = 'John'✓WHERE name = 'John' AND age = 25✓WHERE age = 25✗
覆盖索引
-- 索引包含查询所需的所有列CREATE INDEX idx_covering ON orders(user_id, status, amount);
-- 查询不需要回表SELECT status, amount FROM orders WHERE user_id = 123;索引设计原则
- 为 WHERE, ORDER BY, GROUP BY 创建索引
- 选择性高的列优先
- 避免过多索引(影响写入性能)
- 定期分析索引使用情况
-- MySQL 查看索引使用情况SELECT * FROM sys.schema_unused_indexes;
-- PostgreSQL 查看索引使用情况SELECT * FROM pg_stat_user_indexes;查询优化技巧
1. 避免 SELECT *
-- 不好SELECT * FROM users;
-- 好SELECT id, name, email FROM users;原因:
- 减少网络传输
- 减少 I/O
- 避免不必要的列
2. 使用 LIMIT
-- 不好SELECT * FROM logs ORDER BY created_at DESC;
-- 好SELECT * FROM logs ORDER BY created_at DESC LIMIT 100;3. 避免 OR,使用 UNION
-- 不好SELECT * FROM users WHERE name = 'John' OR age = 25;
-- 好SELECT * FROM users WHERE name = 'John'UNIONSELECT * FROM users WHERE age = 25;4. 使用 JOIN 代替子查询
-- 不好SELECT * FROM usersWHERE department_id IN (SELECT id FROM departments WHERE name = 'IT');
-- 好SELECT u.* FROM users uJOIN departments d ON u.department_id = d.idWHERE d.name = 'IT';5. 避免 LIKE 前导通配符
-- 不好(无法使用索引)SELECT * FROM users WHERE name LIKE '%John%';
-- 好(可以使用索引)SELECT * FROM users WHERE name LIKE 'John%';6. 使用 EXISTS 代替 IN(大数据量)
-- 不好SELECT * FROM orders oWHERE o.user_id IN (SELECT id FROM users WHERE status = 'active');
-- 好SELECT * FROM orders oWHERE EXISTS (SELECT 1 FROM users u WHERE u.id = o.user_id AND u.status = 'active');7. 批量插入
-- 不好INSERT INTO users (name, email) VALUES ('John', 'john@example.com');INSERT INTO users (name, email) VALUES ('Jane', 'jane@example.com');INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
-- 好INSERT INTO users (name, email) VALUES('John', 'john@example.com'),('Jane', 'jane@example.com'),('Bob', 'bob@example.com');8. 使用事务批量操作
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 100);INSERT INTO order_items (order_id, product_id, quantity) VALUES (LAST_INSERT_ID(), 1, 2);
COMMIT;实战案例
案例 1:优化订单查询
问题
-- 查询需要 10 秒SELECT * FROM ordersWHERE user_id = 123ORDER BY created_at DESCLIMIT 20;分析
EXPLAIN SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC LIMIT 20;
-- 结果:type = ALL, key = NULL, rows = 1000000-- 全表扫描,没有使用索引优化
-- 创建复合索引CREATE INDEX idx_user_created ON orders(user_id, created_at DESC);
-- 查询现在只需要 0.01 秒案例 2:优化用户统计
问题
-- 查询每个部门的用户数,需要 5 秒SELECT d.name, COUNT(*)FROM departments dLEFT JOIN users u ON u.department_id = d.idGROUP BY d.id, d.name;优化
-- 使用覆盖索引CREATE INDEX idx_dept_user ON users(department_id, id);
-- 查询现在只需要 0.1 秒案例 3:优化分页查询
问题
-- 越往后越慢SELECT * FROM productsORDER BY idLIMIT 10000, 20;优化
-- 使用游标分页SELECT * FROM productsWHERE id > 10000ORDER BY idLIMIT 20;表设计优化
1. 选择合适的数据类型
-- 不好CREATE TABLE users ( id VARCHAR(36), age VARCHAR(3), is_active VARCHAR(5));
-- 好CREATE TABLE users ( id CHAR(36), -- UUID age TINYINT, -- 0-255 is_active BOOLEAN);2. 使用 NOT NULL
-- 不好CREATE TABLE users ( email VARCHAR(255));
-- 好CREATE TABLE users ( email VARCHAR(255) NOT NULL);3. 规范化 vs 反规范化
规范化
- 减少数据冗余
- 更新更简单
- 查询可能需要更多 JOIN
反规范化
- 数据有冗余
- 查询更快
- 更新需要维护一致性
选择策略:
- 读多写少:考虑反规范化
- 写多读少:考虑规范化
数据库配置优化
MySQL 调优
-- InnoDB 缓冲池大小(建议:可用内存的 50-70%)SET GLOBAL innodb_buffer_pool_size = 4G;
-- 查询缓存(MySQL 8.0 已移除)-- SET GLOBAL query_cache_size = 256M;
-- 连接数SET GLOBAL max_connections = 500;
-- 慢查询SET GLOBAL long_query_time = 1;PostgreSQL 调优
-- 共享缓冲区shared_buffers = 2GB
-- 工作内存work_mem = 64MB
-- 维护工作内存maintenance_work_mem = 512MB
-- 有效缓存大小effective_cache_size = 6GB缓存策略
1. 查询缓存
-- MySQL(使用 Redis 替代)SET GLOBAL query_cache_type = 1;SET GLOBAL query_cache_size = 64M;2. 应用层缓存
# 使用 Redis 缓存def get_user(user_id): cache_key = f"user:{user_id}"
# 先查缓存 user = redis.get(cache_key) if user: return json.loads(user)
# 缓存未命中,查数据库 user = db.query("SELECT * FROM users WHERE id = %s", user_id) redis.setex(cache_key, 3600, json.dumps(user))
return user3. 缓存更新策略
Cache-Aside
def update_user(user_id, data): # 1. 更新数据库 db.execute("UPDATE users SET name = %s WHERE id = %s", data['name'], user_id)
# 2. 删除缓存 redis.delete(f"user:{user_id}")Write-Through
def update_user(user_id, data): # 1. 更新缓存 redis.setex(f"user:{user_id}", 3600, json.dumps(data))
# 2. 更新数据库 db.execute("UPDATE users SET name = %s WHERE id = %s", data['name'], user_id)监控工具
1. Prometheus + Grafana
监控指标:
- 查询响应时间
- 慢查询数量
- 连接数
- 缓存命中率
2. pt-query-digest(MySQL)
pt-query-digest /var/log/mysql/slow.log3. pg_stat_statements(PostgreSQL)
-- 安装扩展CREATE EXTENSION pg_stat_statements;
-- 查询最慢的 SQLSELECT query, calls, total_time, mean_timeFROM pg_stat_statementsORDER BY mean_time DESCLIMIT 10;性能测试
1. 基准测试
# 使用 sysbenchsysbench oltp_read_write \ --db-driver=mysql \ --mysql-host=localhost \ --mysql-port=3306 \ --mysql-user=root \ --mysql-password=password \ --mysql-db=test \ --tables=10 \ --table-size=100000 \ --threads=10 \ --time=60 \ run2. 压力测试
# 使用 ab (Apache Bench)ab -n 10000 -c 100 http://localhost/api/users总结
数据库查询优化是一个系统工程:
分析阶段:
- 开启慢查询日志
- 分析慢查询
- 使用 EXPLAIN
优化阶段:
- 设计合理索引
- 优化 SQL 语句
- 优化表结构
配置阶段:
- 调整数据库参数
- 配置缓存
- 设置监控
测试阶段:
- 基准测试
- 压力测试
- 持续监控
记住:优化不是一次性的,是持续的过程。建立监控体系,定期审查慢查询,持续优化。
相关文章:
数据库查询优化实战:从慢查询到高性能
https://www.599.red/posts/database-query-optimization/