如何在 MySQL 中处理和避免全表扫描?
全表扫描(Full Table Scan)是指 MySQL 查询时未使用索引,逐行扫描表中所有数据,性能极差(百万级数据需几秒甚至几分钟),处理和避免方案如下:
# 1. 识别全表扫描
通过EXPLAIN命令查看执行计划,若出现以下情况,说明是全表扫描:
type=ALL(访问类型为全表扫描);key=NULL(未使用任何索引);rows值接近表的实际行数(预估扫描行数等于表行数)。
# 2. 避免全表扫描的核心方案
- 1. 为查询字段创建合适的索引:
- 为
WHERE子句的过滤字段创建索引(如WHERE age > 20,创建idx_age); - 为
JOIN关联字段创建索引(如user.id = order.user_id,创建order表的idx_userid); - 为
ORDER BY/GROUP BY字段创建索引(如ORDER BY create_time,创建idx_create_time); - 避免过度索引,优先创建联合索引(覆盖多个查询场景)。
- 为
- 2. 优化查询 SQL,避免索引失效:
- 避免在索引字段上做函数操作(如
DATE(create_time)='2024-01-01'改为create_time BETWEEN '2024-01-01 00:00:00' AND '2024-01-01 23:59:59'); - 避免使用
!=、<>、NOT IN、IS NULL(改为BETWEEN、IN、IS NOT NULL的替代方案); - 避免模糊查询前缀通配符(如
LIKE '%张三'改为LIKE '张三%'); - 避免字符串与数字比较(如
phone=13800138000改为phone='13800138000')。
- 避免在索引字段上做函数操作(如
- 3. 控制表的数据量,拆分大表:
- 水平分表:将大表按分表键拆分(如用户表按
user_id%8分 8 张表),减少单表数据量(每张表数据量控制在 100 万以内); - 垂直分表:将大表的冷热数据拆分(如用户表的基本信息和历史订单拆分到两张表),减少查询时扫描的数据量。
- 水平分表:将大表按分表键拆分(如用户表按
- 4. 优化查询条件,减少扫描范围:
- 避免
SELECT *,只查询需要的字段(减少数据传输和内存消耗); - 增加过滤条件,缩小查询范围(如
WHERE create_time > '2024-01-01',过滤旧数据); - 使用
LIMIT限制返回结果(如LIMIT 10,找到匹配数据后立即终止查询)。
- 避免
- 5. 优化 MySQL 配置,提升全表扫描性能(临时方案):
- 增大
innodb_buffer_pool_size(让更多数据缓存到内存,全表扫描时从内存读取,而非磁盘); - 关闭
query_cache(MySQL 8.0 已移除),避免缓存失效带来的额外开销; - 优化
innodb_read_io_threads(增加 IO 线程数,提升磁盘读取速度)。
- 增大
# 3. 必须全表扫描的场景及优化
部分场景无法避免全表扫描(如SELECT COUNT(*) FROM user、SELECT * FROM user WHERE name LIKE '%张三%'),可通过以下方式优化:
- COUNT (*) 优化:
- MyISAM 表:
COUNT(*)直接返回存储的总行数,性能优; - InnoDB 表:避免
COUNT(*)(需扫描全表),改为维护计数器表(如user_count表,记录总行数,新增 / 删除用户时更新计数器);
- MyISAM 表:
- 模糊查询优化:
- 若需
LIKE '%张三%',可使用全文索引(FULLTEXT INDEX),查询时用MATCH AGAINST(如SELECT * FROM user WHERE MATCH(name) AGAINST('张三')); - 用 Elasticsearch 替代 MySQL,处理全文检索场景。
- 若需
上次更新: 12/30/2025