Join返回10000行而非1行,三SQL查询成本差异排查求助
看起来你碰到了两个棘手的问题:Query3的执行成本是另外两个的5倍,而且Join返回了10000行(本该只有1行)。咱们先从行数爆炸的问题入手,因为这大概率是Query3变慢的根源。
一、先搞定Join返回过多行的问题
这种情况基本是关联逻辑错了或者数据匹配出了问题,先排查这几点:
检查你的关联条件是不是写反/写错了
你的prices表的Security是int类型,是关联securities.ID的外键——也就是说,正确的关联应该是p.Security = s.ID。如果你的Query3里写成了p.Security = s.Security(把外键和证券名称列关联),那麻烦就大了:- 一个是int,一个是nvarchar,会触发隐式数据类型转换,直接导致
prices表的主键索引用不上 - 如果
securities表里有不止一行的Security='USD'(哪怕ID不同),Join的时候就会把prices里对应这些ID的所有行都拉出来,直接造成行数爆炸
- 一个是int,一个是nvarchar,会触发隐式数据类型转换,直接导致
先确认
securities表的目标数据唯一性
跑这两条查询看看:SELECT COUNT(*) FROM securities WHERE ID = 75; SELECT COUNT(*) FROM securities WHERE Security = 'USD';如果第一条返回1,第二条返回大于1,说明有多个ID对应同一个证券名称,这时候如果你用
Security='USD'来关联,肯定会匹配到多条securities记录,进而和prices的行产生笛卡尔积,行数直接飙升。
二、再解决Query3执行成本过高的问题
搞定行数问题后,咱们来优化执行效率,核心就是让查询尽量利用现有的索引:
先看看Query3的写法是不是浪费了索引
prices表的主键是(Security, Date),这是个天然的覆盖索引(主键索引包含所有列),所以针对特定Security、特定日期范围的查询应该跑得飞快。比如高效的写法应该是直接过滤:SELECT p.Date, p.Price FROM prices p WHERE p.Security = 75 -- 直接用已知的ID,不用Join securities AND p.Date BETWEEN '2017-10-01' AND '2017-10-03';如果Query3里非要先Join securities再过滤,而且关联条件还错了,数据库就会先做全表扫描或者大表Join,再过滤数据,成本能不高吗?
能不Join就别Join
既然你已经知道目标证券的ID是75,完全可以直接在prices表里用Security=75过滤——除非你需要同时返回证券名称这类其他字段,否则多余的Join纯粹是增加执行成本。看看执行计划找瓶颈
针对Query3,打开SQL Server的执行计划(SSMS里按Ctrl+M启用实际执行计划就行),重点看这几点:- 有没有出现表扫描/聚集索引扫描(如果应该用索引查找的话,说明索引没用到)
- Join的类型:Nested Loops是高效的,要是用了Merge Join或者Hash Join而且涉及大表,成本肯定高
- 执行计划里的“实际行数”和“估计行数”是不是差很多?如果是,说明统计信息过时了,跑下面的语句更新一下:
UPDATE STATISTICS prices; UPDATE STATISTICS securities;
三、给你个优化后的Query3示例
如果确实需要关联securities表(比如要返回证券名称),这么写就没问题:
SELECT s.Security, p.Date, p.Price FROM securities s INNER JOIN prices p ON s.ID = p.Security WHERE s.ID = 75 AND p.Date >= '2017-10-01' AND p.Date <= '2017-10-03';
这个写法的逻辑是:
- 先通过
securities.ID=75找到唯一的一行(用主键索引,飞快) - 再通过
prices.Security=75和日期范围找对应的价格行(用prices的主键索引) - 完全避免了隐式转换,把索引的作用发挥到最大,执行成本会降下来很多
内容的提问来源于stack exchange,提问作者arnoldh

