索引作为数据库查询的"加速引擎",其设计质量直接影响SQL执行效率。以MySQL为例,常见的索引类型包括B+树索引、哈希索引、全文索引及空间索引,每种类型都有其独特的适用场景和性能特点。
B+树索引是关系型数据库中最常用的索引结构,它通过平衡树结构保证了查询效率的稳定性。在百万级数据量的用户订单表中,我们进行了实际测试:对订单时间字段建立B+树索引后,查询某时间段内订单记录的耗时从2.8秒降至0.03秒,性能提升近百倍。
B+树索引的优势主要体现在三个方面:
注意:B+树索引的维护成本较高,频繁的增删改操作会导致索引重建,影响写入性能。建议在读写比较低的表上使用。
哈希索引通过哈希表实现数据的快速定位,理论上可以达到O(1)的时间复杂度。我们在用户登录日志表上测试发现,精确查询某用户的登录记录时,哈希索引的响应时间稳定在0.001秒以内。
但哈希索引存在三个明显局限:
实际应用中,哈希索引最适合用于静态表的精确查找场景,比如配置表、字典表等。在MySQL中,Memory引擎默认使用哈希索引,而InnoDB引擎则提供了自适应哈希索引功能。
复合索引通过多字段组合实现更精准的查询过滤。在设计复合索引时,需要特别注意"最左前缀匹配"原则。我们在用户信息表上建立了(性别,年龄)复合索引,测试发现:
复合索引的字段顺序应该遵循以下原则:
某电商系统的商品表包含1000万条记录,原始查询语句为:
sql复制SELECT * FROM products WHERE category_id = 5 AND status = 1;
执行计划显示该查询触发了全表扫描,耗时3.2秒。通过分析发现category_id和status字段均未建立索引。
优化方案是建立(category_id, status)复合索引。优化后查询耗时降至0.05秒,IO读取量减少90%。这里的关键点在于:
原始报表统计SQL包含嵌套子查询:
sql复制SELECT user_id, (SELECT COUNT(*) FROM orders WHERE user_id = users.id) AS order_count
FROM users;
该语句在用户表与订单表关联时产生大量临时表操作,内存占用高。
优化后使用LEFT JOIN形式:
sql复制SELECT u.user_id, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.user_id;
执行效率提升4倍,内存占用降低60%。优化要点包括:
传统分页查询使用LIMIT OFFSET方式:
sql复制SELECT * FROM orders ORDER BY id DESC LIMIT 100000, 10;
该查询需要扫描前100010条记录后丢弃前100000条,耗时1.2秒。
优化方案采用"游标分页"法:
sql复制SELECT * FROM orders WHERE id < last_seen_id ORDER BY id DESC LIMIT 10;
通过记录上次查询的最大ID实现高效分页,查询耗时降至0.02秒。这种优化方式特别适合移动端无限滚动场景。
type列显示查询的访问类型,性能从优到劣依次为:
当出现ALL时,必须检查索引使用情况。我们在用户查询中发现type为ALL的案例,通过添加适当索引后提升为ref,查询时间从2秒降至0.05秒。
key列显示查询实际使用的索引。若为NULL,则表示未使用索引。常见原因包括:
例如,对varchar字段使用数字条件会导致索引失效:
sql复制SELECT * FROM users WHERE user_id = 10086; -- user_id为varchar类型
优化为显式类型转换:
sql复制SELECT * FROM users WHERE user_id = '10086';
rows列显示MySQL预估的扫描行数。当统计信息不准确时,可能导致优化器选择错误的执行计划。我们遇到过这样一个案例:实际扫描行数为1000,但预估值为100000,导致优化器选择了全表扫描而非索引扫描。
解决方法是通过ANALYZE TABLE命令更新统计信息:
sql复制ANALYZE TABLE users;
以下查询无法利用create_time索引:
sql复制SELECT * FROM users WHERE YEAR(create_time) = 2025;
优化为范围查询:
sql复制SELECT * FROM users WHERE create_time >= '2025-01-01' AND create_time < '2026-01-01';
这种改写方式能够充分利用索引,查询效率提升10倍以上。
当索引字段与查询条件类型不一致时,会发生隐式类型转换导致索引失效。例如:
sql复制SELECT * FROM products WHERE category_id = '5'; -- category_id为int类型
优化为匹配字段类型:
sql复制SELECT * FROM products WHERE category_id = 5;
LIKE查询以通配符开头时无法利用索引:
sql复制SELECT * FROM articles WHERE title LIKE '%优化%';
可以考虑以下替代方案:
MySQL 5.6引入的ICP特性可以在存储引擎层提前过滤数据。例如对于查询:
sql复制SELECT * FROM orders WHERE order_date > '2023-01-01' AND status = 1;
如果存在(order_date, status)复合索引,ICP可以将status=1的条件下推到存储引擎层执行,减少回表次数。
对于时间序列数据,分区表能显著提升查询性能。我们将日志表按月分区后,查询某月数据的性能提升20倍:
sql复制CREATE TABLE logs (
id INT,
log_time DATETIME,
content TEXT
) PARTITION BY RANGE (TO_DAYS(log_time)) (
PARTITION p202301 VALUES LESS THAN (TO_DAYS('2023-02-01')),
PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01')),
...
);
在生产环境中,我们实现了以下架构优化:
这种组合策略使得系统QPS从1000提升到5000,同时保持了99.9%的可用性。
在实际工作中,我总结了以下宝贵经验:
索引不是越多越好:每增加一个索引都会影响写入性能。通常建议单表索引不超过5个。
定期维护索引:使用OPTIMIZE TABLE命令重建表并优化索引:
sql复制OPTIMIZE TABLE orders;
sql复制SELECT * FROM sys.schema_unused_indexes;
注意连接查询的索引:确保连接字段上有索引,特别是外键字段。
批量操作优化:大批量插入时先删除索引,插入后再重建。
使用覆盖索引:查询只使用索引列时性能最佳。
避免过度优化:简单的查询有时比复杂优化更有效。
通过以上优化策略,我们成功将核心业务的数据库查询性能平均提升了10倍,系统响应时间从秒级降至毫秒级。SQL优化是一个需要持续学习和实践的过程,希望这些经验对您有所帮助。