在SQLite的数据库文件里,除了用户自己创建的表和索引,引擎还会悄悄维护一些以sqlite_开头的系统表,其中sqlite_stat1承担着查询优化所需的基础统计职责。当我们在命令行或者程序里执行ANALYZE命令时,SQLite就会把抽样得到的分布数据写进这张表。优化器在生成执行计划前,会读取里面的内容来估算不同访问路径的成本,从而决定是使用全表扫描还是走某个索引。如果这张表缺失或者内容过时,就容易出现明明有索引却不用、或者联表顺序奇怪导致查询拖沓的现象。

sqlite_stat1的表结构与字段解析
sqlite_stat1的schema非常精简,一共有三列。第一列叫tbl,类型是text,存放的是统计对象所属的表名;第二列叫idx,也是text,存放的是索引名或者表名本身(当统计的是没有索引的表时,idx的值和tbl相同);第三列叫stat,同样是text,里面是用空格隔开的一串正整数。这三列合起来,唯一标识了某一个表或者某一个索引的统计行。SQLite在打开数据库并准备编译SQL语句时,会优先从这张表里取数,而不是每次都实时扫描。
对于stat字段里的数字串,其含义需要结合索引定义来看。假设有一个索引定义在表的第2列和第4列上,那么stat里通常会出现类似“100 10 2”这样的内容。第一个数字表示整张表大约有多少行记录;后续每个数字对应索引定义中的一列,表示该列上不同值的大致分组倍数或者每一层的大致行数。优化器利用这些倍数关系,推算出使用这个索引时能过滤掉多少数据。因为只是近似值,所以stat里的数不要求绝对精确,只要数量级合理即可。
需要注意,早期SQLite版本中sqlite_stat1是唯一支持的统计表,后来陆续加入了sqlite_stat2到sqlite_stat4来提供更细的直方图,但sqlite_stat1始终是最基础、必须存在的那一张。即便在最新版本里,如果关闭了高级统计,优化器依然只依赖sqlite_stat1做决策。因此搞清楚它的内容,是排查慢查询的第一步。
统计信息如何被采集与更新
采集动作主要由ANALYZE语句触发。不带参数的ANALYZE会扫描整个库的所有表和索引,并把结果写回sqlite_stat1;也可以写成ANALYZE 表名或者ANALYZE 索引名,只更新特定对象。SQLite在内部会读取表的root page,按页抽样,计算出每个索引列的分布倍数,再格式化成空格分隔的字符串写库。由于是抽样而非全量精确计数,大表的分析速度通常很快,对线上影响较小。
在代码层面,我们可以通过简单的SQL查看当前统计。例如下面的语句能列出所有索引的大致行数:
SELECT tbl, idx, stat FROM sqlite_stat1 WHERE idx <> tbl ORDER BY CAST(substr(stat, 1, instr(stat, ' ') - 1) AS INTEGER) DESC;
上面的查询把stat字段开头的那个总表行数提取出来做了排序,方便我们快速定位大表。如果发现某张明明有几百万行的表,在sqlite_stat1里对应的第一个数字却很小,就说明统计信息严重过期,优化器会误以为表很小而选择错误的计划。此时应当重新执行ANALYZE,或者删除sqlite_stat1中对应行再触发分析。
另外,当使用VACUUM压缩数据库,或者批量导入数据后,统计信息不会自动刷新。很多开发者在导入千万级数据后直接跑报表查询,结果奇慢,查sqlite_stat1才发现还是旧库的几行记录。所以自动化脚本里最好在数据装载完成后显式调用一次ANALYZE,保证sqlite_stat1反映的是真实负载。
基于sqlite_stat1人工干预执行计划
有些极端场景下,ANALYZE给出的统计依然让优化器选错路径,比如数据分布极度倾斜,而抽样刚好错过热点值。这时我们可以手动编辑sqlite_stat1里的stat内容来“欺骗”优化器,引导它使用更合适的索引。SQLite允许直接对sqlite_stat1执行UPDATE,只要保持格式正确,下次编译语句时就会采用新值。当然这种做法只建议用在临时调优,长期还是要靠准确的ANALYZE。
举例来说,某订单表user_id列上建了索引,但少数大客户占据了九成数据。ANALYZE可能算出平均每个user_id对应几百行,优化器便倾向于对大客户也走索引。我们可以把对应索引行的stat改成更大的区分度数字,让优化器意识到用索引回表成本太高,从而对大客户查询改走全表扫描。示例更新语句如下:
UPDATE sqlite_stat1 SET stat = '1000000 5000' WHERE tbl = 'orders' AND idx = 'idx_orders_user';
这里把总行列设为1000000,user_id的区分度设为5000,意味着平均每个用户约200行,比真实倾斜情况显得更分散,优化器便会更保守地使用索引。修改后可以用EXPLAIN QUERY PLAN验证效果。需要强调的是,手动改统计属于危险操作,生产环境务必先在备库验证,并且记录原值以便回滚。
除了手动改值,理解sqlite_stat1还能帮我们判断是否需要创建新索引。如果某张表的stat显示总条数巨大,但现有索引stat的后续倍数都很大,说明列区分度低,单独建索引效果差,这时候就应该考虑联合索引或者调整查询条件,而不是盲目加索引。把sqlite_stat1当成数据库的“体检报告”,能少走很多弯路。
SQLitesqlite_stat1查询优化器修改时间:2026-08-16 06:38:29