Cod*_*eld 15 sql sql-server statistics performance correlation
我们看到这些查询之间存在巨大差异.
慢查询
SELECT MIN(col) AS Firstdate, MAX(col) AS Lastdate
FROM table WHERE status = 'OK' AND fk = 4193
Run Code Online (Sandbox Code Playgroud)
表'表'.扫描计数2,逻辑读取2458969,物理读取0,预读取读取0,lob逻辑读取0,lob物理读取0,lob预读读取0.
SQL Server执行时间:CPU时间= 1966 ms,已用时间= 1955 ms.
快速查询
SELECT count(*), MIN(col) AS Firstdate, MAX(col) AS Lastdate
FROM table WHERE status = 'OK' AND fk = 4193
Run Code Online (Sandbox Code Playgroud)
表'表'.扫描计数1,逻辑读取5803,物理读取0,预读取读取0,lob逻辑读取0,lob物理读取0,lob预读读取0.
SQL Server执行时间:CPU时间= 0 ms,已用时间= 9 ms.
题
查询之间巨大的性能差异之间的原因是什么?
更新 基础上给出意见的问题小更新:
执行顺序或重复执行不会改变性能.没有使用额外的参数,并且(测试)数据库在执行期间没有执行任何其他操作.
慢查询
|--Nested Loops(Inner Join)
|--Stream Aggregate(DEFINE:([Expr1003]=MIN([DBTest].[dbo].[table].[startdate])))
| |--Top(TOP EXPRESSION:((1)))
| |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1008]) WITH ORDERED PREFETCH)
| |--Index Scan(OBJECT:([DBTest].[dbo].[table].[startdate]), ORDERED FORWARD)
| |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[FK]=(5806) AND [DBTest].[dbo].[table].[status]<>'A') LOOKUP ORDERED FORWARD)
|--Stream Aggregate(DEFINE:([Expr1004]=MAX([DBTest].[dbo].[table].[startdate])))
|--Top(TOP EXPRESSION:((1)))
|--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1009]) WITH ORDERED PREFETCH)
|--Index Scan(OBJECT:([DBTest].[dbo].[table].[startdate]), ORDERED BACKWARD)
|--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[FK]=(5806) AND [DBTest].[dbo].[table].[status]<>'A') LOOKUP ORDERED FORWARD)
Run Code Online (Sandbox Code Playgroud)
快速查询
|--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1012],0)))
|--Stream Aggregate(DEFINE:([Expr1012]=Count(*), [Expr1004]=MIN([DBTest].[dbo].[table].[startdate]), [Expr1005]=MAX([DBTest].[dbo].[table].[startdate])))
|--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1011]) WITH UNORDERED PREFETCH)
|--Index Seek(OBJECT:([DBTest].[dbo].[table].[FK]), SEEK:([DBTest].[dbo].[table].[FK]=(5806)) ORDERED FORWARD)
|--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[status]<'A' OR [DBTest].[dbo].[table].[status]>'A') LOOKUP ORDERED FORWARD)
Run Code Online (Sandbox Code Playgroud)

回答
Martin Smith给出的答案似乎解释了这个问题.超短版本是MS-SQL查询分析器错误地在慢查询中使用查询计划,这会导致完整的表扫描.
添加Count(*),查询提示(FORCESCAN)或startdate,FK和status列上的组合索引可修复性能问题.
Mar*_*ith 25
SQL Server基数估计器做出各种建模假设,如
- 独立性:除非有相关信息,否则不同列上的数据分布是独立的.
- 均匀性:在每个统计对象直方图步骤中,不同的值均匀分布,每个值具有相同的频率.
表中有810,064行.
你有查询
SELECT COUNT(*),
MIN(startdate) AS Firstdate,
MAX(startdate) AS Lastdate
FROM table
WHERE status <> 'A'
AND fk = 4193
Run Code Online (Sandbox Code Playgroud)
1,893(0.23%)行符合fk = 4193谓词,其中两行未通过该status <> 'A' 部分,因此整体1,891匹配并需要汇总.
您还有两个索引,它们都不包含整个查询.
对于快速查询,它使用索引fk来直接查找行,fk = 4193然后需要执行1,893次键查找以查找聚簇索引中的每一行以检查status谓词并检索startdatefor聚合.
当您删除COUNT(*)从SELECT列表SQL Server不再具有处理每一个符合条件的行.因此,它考虑另一种选择.
你有一个索引,startdate所以它可以从头开始扫描,执行键查找返回到基表,一旦找到第一个匹配的行停止,就像找到它一样MIN(startdate),同样MAX可以找到另一个扫描开始索引的另一端并向后工作.
SQL Server估计这些扫描中的每一个都会在它们遇到与谓词匹配的行之前处理590行.总共1,180次查找与1,893次查找,因此它选择了此计划.
590的数字是正确的table_size / estimated_number_of_rows_that_match.即,基数估计器假设匹配的行将在整个表中均匀分布.
不幸的是,符合谓词的1,891行不是随机分布的startdate.事实上,它们都被压缩到索引末尾的单个8,205行段中,这意味着扫描到达MIN(startdate)最终需要进行801,859次键查找才能停止.
这可以在下面复制.
CREATE TABLE T
(
id int identity(1,1) primary key,
startdate datetime,
fk int,
[status] char(1),
Filler char(2000)
)
CREATE NONCLUSTERED INDEX ix ON T(startdate)
INSERT INTO T
SELECT TOP 810064 Getdate() - 1,
4192,
'B',
''
FROM sys.all_columns c1,
sys.all_columns c2
UPDATE T
SET fk = 4193, startdate = GETDATE()
WHERE id BETWEEN 801859 and 803748 or id = 810064
UPDATE T
SET startdate = GETDATE() + 1
WHERE id > 810064
/*Both queries give the same plan.
UPDATE STATISTICS T WITH FULLSCAN
makes no difference*/
SELECT MIN(startdate) AS Firstdate,
MAX(startdate) AS Lastdate
FROM T
WHERE status <> 'A' AND fk = 4192
SELECT MIN(startdate) AS Firstdate,
MAX(startdate) AS Lastdate
FROM T
WHERE status <> 'A' AND fk = 4193
Run Code Online (Sandbox Code Playgroud)
您可以考虑使用查询提示强制计划使用索引fk而不是startdate或添加执行计划中突出显示的建议缺失索引(fk,status) INCLUDE (startdate)以避免此问题.
| 归档时间: |
|
| 查看次数: |
10259 次 |
| 最近记录: |