Sea*_*ish 5 index sql-server t-sql index-tuning sql-server-2016
我有一个表,主要用于表示实体之间的关系(即主要由外键组成)。这些关系随时间变化,因此该表具有 StartDate 和 EndDate 列。我现在需要添加开始和结束日期的另一个维度,这意味着可以使用两个不同的日期“镜头”(使用两个日期查询,@Date1 和 @Date2)查看关系,因此架构将如下所示:
MyJoinTable:
| Id | Entity1Id | Entity2Id | StartDate1 | EndDate1 | StartDate2 | EndDate2 |
|----|-----------|-----------|------------|------------|------------|------------|
| 1 | A | B | 1753-01-01 | 2018-09-01 | 1753-01-01 | 2025-01-01 |
| 2 | A | B | 2018-09-01 | 2018-10-01 | 1753-01-01 | 2018-11-01 |
| 3 | A | C | 2018-09-01 | 2018-10-01 | 2018-11-01 | 2025-01-01 |
| 4 | A | B | 2018-10-01 | 2025-01-01 | 1753-01-01 | 2018-11-01 |
| 5 | A | D | 2018-10-01 | 2025-10-01 | 2018-11-01 | 2025-01-01 |
Run Code Online (Sandbox Code Playgroud)
查询主要是连接到这个表,例如:
SELECT e1.Field, e2.Field
FROM Entity1 e1
INNER JOIN MyJoinTable jt ON jt.Entity1Id = e1.Id
AND StartDate1 <= @Date1 AND EndDate1 > @Date1
AND StartDate2 <= @Date2 and EndDate2 > @Date2
INNER JOIN Entity2 e2 ON e2.Id = jt.Entity2Id
Run Code Online (Sandbox Code Playgroud)
我的问题是:
我没有时间进行广泛的测试,但可以建议从哪里开始。
\n如果以更对称的方式重写查询,\ne强调两个实体都连接到 的二维\n横截面MyJoinTable:
SELECT E1.Field, E2.Field\nFROM MyJoinTable JT\nJOIN Entity1 E1 ON E1.Id = JT.Entity1Id\nJOIN Entity2 E2 ON E2.Id = JT.Entity2Id\nWHERE\n StartDate1 <= @date1 AND EndDate1 > @date1 AND\n StartDate2 <= @date2 AND EndDate2 > @date2\nRun Code Online (Sandbox Code Playgroud)\n您将看到一种相当有效的执行方法是首先\n提取该横截面,然后将实体连接到其中。\n以下等效查询将用于说明目的\n(但不要使用它!):
\n-- A sample to demostrate the desired order or execution:\nSELECT E1.Field, E2.Field\nFROM\n-- 1. Calculate the cross-section:\n( SELECT JT.Entity1Id, JT.Entity2Id\n FROM MyJoinTable JT\n WHERE \n StartDate1 <= @date1 AND EndDate1 > @date1 AND\n StartDate2 <= @date2 AND EndDate2 > @date2\n) SECT\n-- 2. Join the entity tables:\nJOIN Entity1 E1 ON E1.Id = SECT.Entity1Id\nJOIN Entity2 E2 ON E2.Id = SECT.Entity2Id\nRun Code Online (Sandbox Code Playgroud)\n既然你说只有
\n\n\n为任何 @Date1、@Date2 组合返回一个关系行。
\n
因此,连接之前的实体MyJoinTable会减少\n到所需的横截面,@date1效率@date2很低,因为\n每个实体可能有许多记录,因此结果\n将包含太多行。实体最好在最后使用它们的自然键字段 进行连接,Id我假设它是聚集索引。因此,下面的解决方案提出了计算此横截面的不同方法,对应于SECT示例的子查询\上面\xe2\x80\x94
让我们尝试向原始查询添加有用的索引。由于MSSQL的复合索引是分层的,因此它们在优化间隔比较方面毫无用处,因此我们对给定结构能做的最好的事情就是为其中一个日期字段建立索引,但要确保覆盖所有其他字段所需表格:
\nCREATE NONCLUSTERED INDEX SD1 ON MyJoinTable ( StartDate1 )\nINCLUDE (Entity1Id, Entity2Id, StartDate2, EndDate1, EndDate2 )\nRun Code Online (Sandbox Code Playgroud)\n然后查询将按以下顺序执行:
\n使用索引SD1,对 \n 执行索引查找MyJoinTable以按 查找记录StartDate1,并按StartDate2、\nEndDate1和EndDate2\n残差谓词对其进行过滤
使用自然键连接实体表Id。
此方法效率不高,因为四个日期谓词中只有一个由索引完全优化,剩余谓词是令人厌烦的数据研磨机。
\n获得二维横JOIN截面的另一种对称方法是通过INTERSECT计算四个日期谓词的结果:
SELECT Entity1Id, Entity2Id\nFROM\n( SELECT Id, Entity1Id, Entity2Id FROM MyJoinTable\n WHERE @date1 >= StartDate1\n INTERSECT\n SELECT Id, Entity1Id, Entity2Id FROM MyJoinTable\n WHERE @date2 >= StartDate2\n INTERSECT\n SELECT Id, Entity1Id, Entity2Id FROM MyJoinTable\n WHERE @date1 < EndDate1\n INTERSECT\n SELECT Id, Entity1Id, Entity2Id FROM MyJoinTable\n WHERE @date2 < EndDate2 )\nSECT\nRun Code Online (Sandbox Code Playgroud)\n有了合适的指数,
\nCREATE NONCLUSTERED INDEX SD1 ON MyJoinTable (StartDate1)\n INCLUDE (Id, Entity1Id, Entity2Id)\nCREATE NONCLUSTERED INDEX SD2 ON MyJoinTable (StartDate2)\n INCLUDE (Id, Entity1Id, Entity2Id)\nCREATE NONCLUSTERED INDEX ED1 ON MyJoinTable (EndDate1 )\n INCLUDE (Id, Entity1Id, Entity2Id)\nCREATE NONCLUSTERED INDEX ED2 ON MyJoinTable (EndDate2 )\n INCLUDE (Id, Entity1Id, Entity2Id)\nRun Code Online (Sandbox Code Playgroud)\n该查询的计划仅包括干净的索引查找(没有剩余谓词)和哈希匹配操作。
\n观察到查询和索引中的实体键呈现\n四倍冗余,可以通过单独加入实体来删除\xe2\x80\x94,但代价是\n更复杂的执行计划\xe2\x80\x94:
\nSELECT JT.Entity1Id, JT.Entity2Id\nFROM\n( SELECT Id FROM MyJoinTable WHERE @date1 >= StartDate1\n INTERSECT\n SELECT Id FROM MyJoinTable WHERE @date2 >= StartDate2\n INTERSECT\n SELECT Id FROM MyJoinTable WHERE @date1 < EndDate1\n INTERSECT\n SELECT Id FROM MyJoinTable WHERE @date2 < EndDate2 )\nSECT\nJOIN MyJoinTable JT ON JT.Id = SECT.Id\nRun Code Online (Sandbox Code Playgroud)\n与这些指数:
\nCREATE NONCLUSTERED INDEX SD1 ON MyJoinTable (StartDate1) INCLUDE (Id)\nCREATE NONCLUSTERED INDEX SD2 ON MyJoinTable (StartDate2) INCLUDE (Id)\nCREATE NONCLUSTERED INDEX ED1 ON MyJoinTable (EndDate1 ) INCLUDE (Id)\nCREATE NONCLUSTERED INDEX ED2 ON MyJoinTable (EndDate2 ) INCLUDE (Id)\nRun Code Online (Sandbox Code Playgroud)\n但是,由于在任何一种情况下,四个日期谓词中的每一个都仅限制日期\n非一端,并不能充分减少数据量,因此散列\n匹配可能必须\n将数据溢出\n到tempdb. 如果是这样,则此方法不适合您的\n环境。
现在我们可以合并解决方案 I 和 II,以便提出一个不需要太多 RAM 同时速度相当快的计划:
\nSELECT Entity1Id, Entity2Id FROM\n( SELECT Id, Entity1Id, Entity2Id FROM MyJoinTable\n WHERE @date1 >= StartDate1 AND @date1 < EndDate1\n INTERSECT\n SELECT Id, Entity1Id, Entity2Id FROM MyJoinTable\n WHERE @date2 >= StartDate2 AND @date2 < EndDate2 )\nSECT\nRun Code Online (Sandbox Code Playgroud)\n现在,日期约束在丢弃数据方面更加有效\n因为它们限制了两端的日期。指数如下:
\nCREATE NONCLUSTERED INDEX SE1 ON MyJoinTable (StartDate1)\n INCLUDE (EndDate1, Entity1Id, Entity2Id)\nCREATE NONCLUSTERED INDEX SE2 ON MyJoinTable (StartDate2)\n INCLUDE (EndDate2, Entity1Id, Entity2Id)\nRun Code Online (Sandbox Code Playgroud)\n每个约束都使用带有残差谓词的索引查找,这比解决方案 I 中的单个索引查找更好,并且比解决方案 II 占用更少的 RAM。该计划显示了三种并行化机会:\n 一个用于每个日期约束,一个用于操作INTERSECT。
这种方法的非冗余版本是:
\nSELECT JT.Entity1Id, JT.Entity2Id FROM\n( SELECT Id FROM MyJoinTable\n WHERE @date1 >= StartDate1 AND @date1 < EndDate1\n INTERSECT\n SELECT Id FROM MyJoinTable\n WHERE @date2 >= StartDate2 AND @date2 < EndDate2 )\nSECT\nJOIN MyJoinTable JT ON JT.Id = SECT.Id\nRun Code Online (Sandbox Code Playgroud)\n带索引
\nCREATE NONCLUSTERED INDEX SE1 ON MyJoinTable (StartDate1)\n INCLUDE (EndDate1, Id)\nCREATE NONCLUSTERED INDEX SE2 ON MyJoinTable (StartDate2)\n INCLUDE (EndDate2, Id)\nRun Code Online (Sandbox Code Playgroud)\n虽然它应该更好地处理您的数据,其中SECT子查询\n最多返回一行,但在我对随机生成的数据进行的粗略测试中\n效率较低,因为服务器使用了哈希匹配\n而不是与该单个数据的嵌套循环连接排。请在你身边尝试一下。
可以优化查询,但代价是引入更复杂的结构,需要额外的维护并减慢MyJoinTable. 如果您愿意\n支付价格,请将日期范围存储为天数:
CREATE TABLE MyJoinTable\n( Id INT IDENTITY(1,1),\n Entity1Id INT,\n Entity2Id INT,\n Range1 INT, -- reference to Ranges.Id \n Range2 INT -- reference to Ranges.Id\n)\n\nCREATE TABLE Ranges\n( Id INT,\n Date Date\n)\nRun Code Online (Sandbox Code Playgroud)\n并查询关系:
\nSELECT E1.Field, E2.Field\nFROM MyJoinTable JT\nJOIN Entity1 E1 ON E1.Id = JT.Entity1Id\nJOIN Entity2 E2 ON E2.Id = JT.Entity2Id\nJOIN Ranges R1 ON R1.Id = JT.Range1\nJOIN Ranges R2 ON R2.Id = JT.Range2\nWHERE\n R1.Date = @date1 AND\n R2.Date = @date2\nRun Code Online (Sandbox Code Playgroud)\n您将需要一些测试才能确定最佳索引,但我认为以下应该有效:
\nCREATE NONCLUSTERED INDEX RD ON Ranges ( Date ) INCLUDE ( id )\nCREATE NONCLUSTERED INDEX RR ON MyJoinTable ( Range1, Range2 )\n-- optionally: INCLUDE (Entity1Id, Entity2Id)\nRun Code Online (Sandbox Code Playgroud)\n索引RD将确保快速找到与指定日期相对应的范围,并且索引RR将有助于查找与这些范围匹配的记录。但是,您必须设计一种方法\n填充表格Ranges并使其与 保持同步MyJoinTable,\n因为手动这样做是不可能的。