一、那些年我们遇到的查询困境
最近隔壁工位的小王又双叒叕在薅头发了,不用问准是那个"客户综合查询"功能又卡死了。咱们做后台系统的都懂,当数据量突破百万级,各种筛选条件排列组合,查询速度就会像早高峰的北京三环一样让人崩溃。常见症状包括:
- 查询响应时间超过10秒
- 数据库CPU使用率长期90%+
- 前端页面loading转圈永无止境
这种情况往往出现在需要同时满足多个筛选条件的场景,比如电商平台的商品搜索(价格区间+商品类别+地域限制+库存状态),又或是金融系统的交易记录查询(时间范围+交易类型+金额区间+用户等级)。
二、优化之索引篇
2.1 联合索引的正确打开方式
-- 创建联合索引(MySQL 8.0)
CREATE INDEX idx_composite_search ON orders
(order_status, payment_type, create_time DESC)
COMMENT '针对常用查询条件的组合索引';
/* 查询示例:
筛选已支付、信用卡支付、最近三个月订单
注意最左匹配原则的应用 */
SELECT * FROM orders
WHERE order_status = 'PAID'
AND payment_type = 'CREDIT_CARD'
AND create_time > DATE_SUB(NOW(), INTERVAL 3 MONTH);
这里有个常见误区:很多开发者喜欢给每个字段单独建索引。但实际测试发现,对包含status、type、time三个条件的查询,联合索引比三个单列索引快8-12倍。
2.2 函数索引的黑科技
-- 为JSON字段创建函数索引(MySQL 5.7+)
ALTER TABLE products
ADD INDEX idx_product_tags ((CAST(product_info->'$.tags' AS CHAR(255))));
/* 查询示例:
查找包含"有机食品"标签的商品
注意JSON_EXTRACT的用法 */
SELECT product_name
FROM products
WHERE JSON_CONTAINS(
product_info->'$.tags',
'"organic"',
'$'
);
这种索引特别适合处理JSON类型字段的查询,实测可使JSON字段查询速度提升5倍以上,但要注意索引长度限制。
三、查询语句
3.1 OR条件的优雅变形
-- 原始慢查询
SELECT * FROM user_logs
WHERE operation_type = 'LOGIN'
OR ip_address LIKE '192.168%'
OR user_agent LIKE '%Chrome%';
-- 优化后的UNION写法
(SELECT * FROM user_logs WHERE operation_type = 'LOGIN')
UNION
(SELECT * FROM user_logs WHERE ip_address LIKE '192.168%')
UNION
(SELECT * FROM user_logs WHERE user_agent LIKE '%Chrome%');
当遇到多个OR条件时,UNION方式可以使每个子查询都能命中索引。测试数据显示,当单表数据量超过50万时,这种改写可使查询速度提升3-5倍。
3.2 分页查询的深度优化
-- 传统分页(数据量大时性能差)
SELECT * FROM articles
ORDER BY create_time DESC
LIMIT 100000, 20;
-- 优化版分页(需配合索引)
SELECT * FROM articles
WHERE create_time < '2023-06-01' -- 记住上一页最后的时间
ORDER BY create_time DESC
LIMIT 20;
这种"游标分页"法在千万级数据场景下,可以将分页查询时间从5秒缩短到200ms以内,但需要前端配合记录边界值。
四、表结构
4.1 垂直拆分的艺术
-- 原始表结构
CREATE TABLE user (
id INT PRIMARY KEY,
username VARCHAR(50),
password_hash CHAR(64),
profile_json JSON, -- 包含大量不常用信息
last_login DATETIME,
INDEX idx_login (last_login)
);
-- 拆分后结构
CREATE TABLE user_auth (
id INT PRIMARY KEY,
username VARCHAR(50),
password_hash CHAR(64),
last_login DATETIME,
INDEX idx_login (last_login)
);
CREATE TABLE user_profile (
user_id INT PRIMARY KEY,
profile_json JSON
);
通过将高频查询字段(登录时间)和低频大字段(个人资料)分离,实测查询性能提升40%,同时减少了30%的索引存储空间。
4.2 归档表的时光机
-- 创建历史表(与现网表结构一致)
CREATE TABLE orders_archive LIKE orders;
-- 数据迁移(每次迁移前三个月数据)
INSERT INTO orders_archive
SELECT * FROM orders
WHERE create_time < DATE_SUB(NOW(), INTERVAL 3 MONTH);
-- 原表删除已归档数据
DELETE FROM orders
WHERE create_time < DATE_SUB(NOW(), INTERVAL 3 MONTH);
在电商大促场景下,这种冷热数据分离策略使核心订单表的查询速度提升60%,但要注意归档作业的时间窗口选择。
五、执行计划
5.1 EXPLAIN的完全解读
EXPLAIN FORMAT=JSON
SELECT customer_id, SUM(amount)
FROM transactions
WHERE transaction_date BETWEEN '2023-01-01' AND '2023-06-30'
AND status = 'COMPLETED'
GROUP BY customer_id
HAVING SUM(amount) > 10000;
/* 关键指标解读:
"using_index": true 表示索引覆盖
"rows_estimated": 数值越小越好
"filesort": 出现则说明需要优化排序 */
通过分析执行计划,我们发现当出现"Using temporary"时,查询性能通常会下降70%以上,此时需要考虑增加合适的索引。
5.2 强制索引的妙用
SELECT /*+ INDEX(transactions idx_status_date) */ *
FROM transactions FORCE INDEX (idx_status_date)
WHERE status = 'PENDING'
AND expiration_date < NOW();
在索引统计信息不准的特殊情况下,强制索引可以使查询时间从8秒降至1秒内,但要慎用,避免因数据分布变化导致适得其反。
六、缓存机制的太极之道
6.1 查询缓存的正确姿势
-- 启用查询缓存(适合读多写少场景)
SET GLOBAL query_cache_type = ON;
SET GLOBAL query_cache_size = 134217728; -- 128MB
/* 需要注意:
1. 表数据变更时会自动失效相关缓存
2. 写操作频繁的表不宜开启
3. SQL语句必须完全一致才能命中 */
在新闻类网站的首页查询中,开启查询缓存后,相同查询的响应时间从200ms降至5ms,缓存命中率达到85%。
6.2 应用层缓存的组合拳
// Spring Boot + Redis缓存示例
@Cacheable(value = "userProfile", key = "#userId")
public UserProfile getUserProfile(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException("用户不存在"));
}
通过二级缓存策略,在高并发场景下,数据库QPS从5000+降至300左右,系统吞吐量提升8倍。
七、硬件优化
7.1 磁盘配置的黄金法则
# 查看InnoDB配置状态
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';
/* 关键指标:
Innodb_buffer_pool_read_requests: 缓存读取次数
Innodb_buffer_pool_reads: 物理磁盘读取次数
缓存命中率 = 1 - (reads/read_requests) */
某金融系统将InnoDB缓冲池从4GB扩大到32GB后,缓存命中率从75%提升到99%,查询平均响应时间降低90%。
7.2 连接池的太极之道
// HikariCP配置示例(application.yml)
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
合理配置连接池后,系统在万人同时在线场景下的数据库连接等待时间从3秒降至200ms,连接泄漏问题减少80%。
八、避坑指南与终极心法
8.1 十大常见误区
- 索引越多越好 → 实际每增加一个索引,写操作会慢2-5%
- 所有字段都建索引 → 导致索引文件比数据文件还大
- 频繁使用SELECT * → 增加I/O消耗和网络传输
- 忽视隐式类型转换 → 如WHERE id = '100'导致索引失效
- 过度分库分表 → 增加系统复杂度
- 盲目使用存储过程 → 不利于水平扩展
- 长期不维护统计信息 → 导致执行计划错误
- 使用全文索引代替ES → 查询效率差10倍以上
- 事务范围过大 → 锁竞争激烈
- 忽视慢查询日志 → 错失优化机会
8.2 性能优化四象限法则
紧急且重要
↗(立刻处理)
查询时间>5s →│
索引缺失 →│
↘(长期规划)
重要不紧急
根据故障影响面和修复成本,建立优化优先级矩阵,通常建议优先处理耗时超过3秒的查询。