数据库在几百万行里找一行

为什么需要

读者打开一道菜谱、按配料找菜、看自己收藏了哪些菜,每一次,服务器都要向提一个问题,叫(query),比如「找出第 1024 号菜谱」。查询是一种:只把数据找出来,不改变它。

百家菜越办越大,菜谱表有了 300 万行。数据库怎样在 300 万行里找出第 1024 号?

怎样做

数据库不保证表里的行按编号排好,第 1024 号可能在任何位置。没有别的办法时,数据库只能从第一行起逐行翻看,这叫(full table scan):运气好,翻几行就到;平均要翻一半,150 万行;要找的菜已经删掉时,非得翻完全部 300 万行,才能确定没有。

快的办法是(index):为某一列另外整理一份按顺序排好的清单,每一项记着这一行在表里的位置,表本身不动。这像书末的索引,词按顺序排好,翻到要找的词,就知道它在第几页。找的时候先看清单正中间的一项:要找的比它小,就只看前一半,比它大就只看后一半。每看一次,范围缩小一半。

300 万对半分下去,最多看 22 次,就能确定有没有。100 万行约 20 次,200 万行约 21 次:行数翻一倍,只多看 1 次。编号是主键,设了主键的列,数据库通常会自动替它建这样一份索引。为了看清它帮了多大的忙,下图可以先假设没有它,再换成有索引,各找一次。

找一行:没有索引时逐行翻看,有索引时对半找

先看这里先选「假设没有索引」找第 1024 号,看翻看的次数怎样一路上涨;再换成「有编号的索引」找一次,比一比次数,也看看图下「每新增一行要做的工作」那根条。

索引
要找
菜谱表:300 万行,不按编号排
第几行编号菜名
11,624,801宫保鸡丁
2469,622土豆炖牛肉
31,955,887麻婆豆腐
42,686,889酸辣汤
51,900,252宫保鸡丁
62,092,683土豆炖牛肉
72,126,288麻婆豆腐

找一行的示意图:一边是菜谱表的一段,高亮的是正在翻看的那一行;有索引时,另一边每看一次就多一条横条,表示这一次还要找的那一段,一条比一条窄,太窄时放大成整条接着画;找到以后,表跳到索引指向的那一行。翻看的次数和结果写在图下的读数里。

还没开始。选好要找的菜,按「下一步」,也可以直接看结果。

翻看的次数(每看一个编号算一次)—
结果—
每新增一行,要改几处(示意)1 处
每新增一行要做的工作(示意)

没有索引:只写进表这一处。

表里每一行的编号、菜名和位置都是为演示虚构的,每次打开都一样;同一个菜名可能有好几道,编号各不相同。没有索引时数的是翻看了几行,有索引时数的是在索引里看了几个编号,每看一个编号算一次,两种用的是同一个单位。

查询常用(SQL)来写,大多用它。它只说要什么,比如「编号是 1024 的那一行」,不说怎样找,用不用索引由数据库自己决定。

索引也可以建在别的列上。要找用到番茄的菜,就给配料表的配料名一列建一份索引:写着番茄的几项排在一起,对半找到第一项,再顺着往下读。索引只帮得上按这一列找的查询;很小的表也不必建,直接翻反而更快。

代价是什么

书末的索引这个比方有一处不成立:书印好以后不再变,索引编一次就够;表却随时在变。每新增或删除一行,或者改到被索引的那一列,数据库都要顺手改索引:先在索引里对半找到位置,300 万项也要看二十来次,再写进或删去一笔。每多一个索引,就多一份这样的工作,写入就慢一些。

索引本身也要占硬盘,表越大,它也越大。所以索引不是越多越好:常按某一列找、每次只要其中几行,才值得为它建一个;写得多、很少按它找的列,就不必建。

资料来源

以下资料均于 2026-10-23 访问。

  1. 2.2. ConceptsPostgreSQL 全球开发组(PostgreSQL 18.6 文档),未注明日期
  2. Avoiding Full Table ScansOracle(MySQL 8.4 参考手册),未注明日期
  3. 11.1. Introduction (Indexes)PostgreSQL 全球开发组(PostgreSQL 18.6 文档),未注明日期
  4. Query PlanningSQLite,2026-03-22
  5. 5.5. ConstraintsPostgreSQL 全球开发组(PostgreSQL 18.6 文档),未注明日期
  6. How MySQL Uses IndexesOracle(MySQL 8.4 参考手册),未注明日期
  7. 10.3 Optimization and IndexesOracle(MySQL 8.4 参考手册),未注明日期