查找表中的一行数据,数据库有几种实现方法?
答案是两种,全表扫描或者索引查找。
全表扫描就是通过读取整个表中的数据获取查询结果,这种方式最大的问题就是随着数据量的增长,扫描磁盘数据的性能急剧下降。对于 MySQL,我们可以使用 EXPLAIN 命令查看 SQL 语句的执行计划,例如(示例数据):
EXPLAIN
SELECT *
FROM employee;
Name |Value |
-------------+--------+
id |1 |
select_type |SIMPLE |
table |employee|
partitions | |
type |ALL |
possible_keys| |
key | |
key_len | |
ref | |
rows |25 |
filtered |100.0 |
Extra | |
从以上查询计划的输出结果可以看出,type 字段的值为 ALL,表示全表扫描。
索引查找则是通过索引(通常是 B+树、B*树)快速定位数据。以下示例通过主键查找员工信息:
EXPLAIN
SELECT *
FROM employee
WHERE emp_id = 10;
Name |Value |
-------------+--------+
id |1 |
select_type |SIMPLE |
table |employee|
partitions | |
type |const |
possible_keys|PRIMARY |
key |PRIMARY |
key_len |4 |
ref |const |
rows |1 |
filtered |100.0 |
Extra | |
输出中的 type 字段值为 const,表示通过主键或者唯一索引查找数据,并且最多返回一条记录。这是一种非常快速的访问方式,所以相当于一个常量(constant)。
通过索引查找数据时,也可能使用索引范围扫描。例如:
EXPLAIN
SELECT *
FROM employee
WHERE emp_id BETWEEN 10 AND 12;
Name |Value |
-------------+-----------+
id |1 |
select_type |SIMPLE |
table |employee |
partitions | |
type |range |
possible_keys|PRIMARY |
key |PRIMARY |
key_len |4 |
ref | |
rows |3 |
filtered |100.0 |
Extra |Using where|
输出中的 type 字段值为 range,表示通过主键索引的范围扫描获取数据。
一般而言,通过索引找出数据比全表扫描更加高效,具体分析可以参考这篇文章。但是仍然存在一些情况下,全表扫描是更好的选择,具体包括:
- 表中的数据量很小,以至于全表扫描比索引查找更快。尤其是基于辅助索引的范围扫描,因为扫描索引之后还需要回表查询数据,这种查询是随机 IO。例如,数据量少于 10 条的配置表就可以通过全表扫描快速获取数据。
- 查询语句中没有基于索引字段的过滤条件;或者基于索引的查询条件需要返回表中的一大部分数据,导致全表扫描速度更快。一种应用场景就是数据仓库中的聚合分析,通常需要汇总整个表中的数据。
- 索引的基数(不同值)太小,例如性别字段的单独索引。这种情况下,MySQL 会认为索引需要查找很多记录,性能不如全表扫描。
执行计划中出现全表扫描,并不一定代表查询存在性能问题,也可能是 MySQL 优化器经过分析之后的正确选择。如果我们确认全表扫描不是最优方法,可以通过一些技术手段帮助优化器选择其他实现方法,例如使用 ANALYZE TABLE 命令更新统计信息,使用索引提示 FORCE INDEX 强制使用某个索引,或者使用系统变量 max_seeks_for_key 控制索引扫描查找的最大记录数。