为何SQLite中相关子查询比MAX()函数查询性能高出如此之多?
这问题挺有意思的,看起来都是用了同一个覆盖索引,但性能差了好几个数量级,核心原因在于两个查询的执行逻辑和数据处理量完全不在一个量级,咱们一步步拆解:
先看第一个GROUP BY MAX()查询
你的语句是:
SELECT stockID, MAX(PriceDate) AS MaxDate FROM StockPrices GROUP BY stockID
执行计划显示是SCAN TABLE StockPrices USING COVERING INDEX sqlite_autoindex_StockPrices_1——这里的"SCAN"是关键:它意味着SQLite需要遍历整个StockPrices的覆盖索引(6000万条记录),对每个stockID的所有PriceDate逐一比较,找出最大值。
哪怕这个索引是按(StockID, PriceDate)排序的主键索引,SQLite的GROUP BY聚合逻辑在这里还是得从头到尾扫完所有条目。6000万条记录的遍历,哪怕是内存里的索引,IO和计算成本都是极高的,这就是它耗时21秒的原因。
再看第二个相关子查询
你的语句是:
SELECT stockID, (SELECT PriceDate FROM StockPrices AS sp WHERE s.stockID= sp.stockID ORDER BY PriceDate DESC LIMIT 1) AS MaxDate FROM Stocks AS s
执行计划里的关键是SEARCH TABLE StockPrices AS sp USING COVERING INDEX sqlite_autoindex_StockPrices_1 (StockListingID=?)——这里是"SEARCH"而不是"SCAN",意思是对每个Stocks里的stockID,SQLite可以直接利用索引定位到该stockID对应的所有记录的末尾(因为索引是按StockID升序、PriceDate升序排列的,所以同一stockID下最后一条记录的PriceDate就是最大的),然后只取那一条记录。
Stocks表只有27000行,相当于只需要执行27000次高效的索引查找(每次查找是O(log n)的时间复杂度),总操作量和6000万条的遍历完全不是一个级别,这就是它只花3毫秒的核心原因。
为什么执行计划看起来没体现出差异?
执行计划只告诉你用了哪个索引,但没告诉你处理的记录数量。第一个查询处理6000万条,第二个只处理27000条+27000次索引定位,差距自然天差地别。
另外补充一点:两个查询的结果集其实有细微差异——第一个会返回StockPrices中所有存在的stockID(包括那些不在Stocks表中的),而第二个只返回Stocks表中存在的stockID,这刚好贴合你"获取每只股票的最新价格日期"的需求,相当于顺便过滤了无效数据。
总结一下性能差距的根源
- GROUP BY MAX()需要全局遍历所有6000万条记录,进行全量聚合;
- 相关子查询只需要遍历27000行的Stocks表,对每个stockID执行一次精准的索引定位,直接取到最大PriceDate;
- 你的StockPrices主键索引结构(
StockID, PriceDate, ...)让子查询可以直接定位到目标记录,而GROUP BY MAX()无法利用索引的有序性提前终止扫描,必须扫完所有数据。
内容的提问来源于stack exchange,提问作者spatbord




