我应该使用强制转换或范围将日期时间加入日期吗?

Mat*_*hew 3 t-sql sql-server-2008-r2 sql-server-2012 datetime

这个问题是从这里提出的优秀问题中提取出来的:

迄今为止的演员阵容是可以讨论的,但这是一个好主意吗?

就我而言,我不关心该WHERE子句,而是关心加入具有类型列的事件表DATE

一个表有DATETIME2,另一个有DATE......所以我可以有效地JOIN使用aCAST( AS DATE)或者我可以使用“传统”范围查询(>=日期和<日期+1)。

我的问题是哪个更可取?这些DATETIME几乎永远不会与谓词DATE值匹配。

我希望保持在 200 万行DATETIME和 5000行以下DATE(如果这个考虑有所不同)

我是否应该期望在JOIN使用该WHERE子句时具有相同的行为?我应该更喜欢哪个通过缩放来保持性能?答案是否随 MSSQL 2012 而改变?

我的通用用例是将我的事件表视为日历表

SELECT
    events.columns
    ,SOME_AGGREGATIONS(tasks.column)
FROM
    events
LEFT OUTER JOIN
    tasks
        --This appropriately states my intent clearer
        ON CAST(tasks.datetimecolumn AS DATE) = events.datecolumn 
        --But is this more effective/scalable?
        --ON tasks.datetimecolumn >= events.datecolumn 
        --AND tasks.datetimecolumn < DATEADD(day,1,events.datecolumn)
GROUP BY
    events.columns
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 6

“这取决于”。

=谓词和cast迄今为止的优点之一是连接可以是散列或合并。范围版本将强制执行嵌套循环计划。

如果没有有用的索引来寻找datetimecolumntasks这将产生重大影响。

设置问题中提到的5K/200万行测试数据

CREATE TABLE events
  (
     eventId    INT IDENTITY PRIMARY KEY,
     datecolumn DATE NOT NULL,
     details    CHAR(1000) DEFAULT 'D'
  )

INSERT INTO events
            (datecolumn)
SELECT TOP 5000 DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY @@SPID), GETDATE())
FROM   spt_values v1,
       spt_values v2

CREATE TABLE tasks
  (
     taskId         INT IDENTITY PRIMARY KEY,
     datetimecolumn DATETIME2 NOT NULL,
     details        CHAR(1000) DEFAULT 'D'
  );

WITH N
     AS (SELECT number
         FROM   spt_values
         WHERE  number BETWEEN 1 AND 40
                AND type = 'P')
INSERT INTO tasks
            (datetimecolumn)
SELECT DATEADD(MINUTE, number, CAST(datecolumn AS DATETIME2))
FROM   events,
       N
Run Code Online (Sandbox Code Playgroud)

然后开启

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
Run Code Online (Sandbox Code Playgroud)

并尝试CAST版本

SELECT events.eventId,
       MAX(tasks.details)
FROM   events
       LEFT OUTER JOIN tasks
         ON CAST(tasks.datetimecolumn AS DATE) = events.datecolumn
GROUP  BY events.eventId
Run Code Online (Sandbox Code Playgroud)

7.4 秒内完成

Table 'Worktable'. Scan count 0, logical reads 0
Table 'tasks'. Scan count 1, logical reads 28679
Table 'events'. Scan count 1, logical reads 719

   CPU time = 3042 ms,  elapsed time = 7434 ms.
Run Code Online (Sandbox Code Playgroud)

从连接出来并进入连接的估计行数GROUP BY太小(5006.27 对实际 2,000,000)并且散列聚合溢出到tempdb

方案一

尝试范围谓词

SELECT events.eventId,
       MAX(tasks.details)
FROM   events
       LEFT OUTER JOIN tasks
         ON tasks.datetimecolumn >= events.datecolumn
            AND tasks.datetimecolumn < DATEADD(day, 1, events.datecolumn)
GROUP  BY events.eventId 
Run Code Online (Sandbox Code Playgroud)

缺少相等谓词会强制执行嵌套循环计划。由于没有有用的索引来支持这个查询,它别无选择,只能扫描 200 万行表 5,000 次。

在我的机器上,它给出了一个最终在 1 分 40 秒后完成的并行计划。

Table 'tasks'. Scan count 4, logical reads 143390000
Table 'events'. Scan count 5, logical reads 788
Table 'Worktable'. Scan count 0, logical reads 0
  CPU time = 368193 ms,  elapsed time = 100528 ms.
Run Code Online (Sandbox Code Playgroud)

这一次的行数出来的加入而进入骨料严重过度估计(在估计124939000 VS实际2000000)

方案二

在更改表以使相应的日期/时间列成为集群主键后重复实验,结果会改变。

这两个查询最终都选择了嵌套循环计划。的CAST作为DATE版本给串行版本4.5秒完成,范围版本并行计划,在经过时间的3.2秒CPU时间1.1秒完成。

应用于MAXDOP 1第二个查询以使数字更易于比较会返回以下结果。

查询 1

Table 'Worktable'. Scan count 0, logical reads 0
Table 'tasks'. Scan count 5000, logical reads 78137
Table 'events'. Scan count 1, logical reads 719
   CPU time = 3167 ms,  elapsed time = 4497 ms.
Run Code Online (Sandbox Code Playgroud)

查询 2

Table 'tasks'. Scan count 5000, logical reads 49440
Table 'events'. Scan count 1, logical reads 719
   CPU time = 3042 ms,  elapsed time = 3147 ms.
Run Code Online (Sandbox Code Playgroud)

查询 1 估计有 5006.73 行来自连接,散列聚合tempdb再次溢出。

查询 2 再次高估了很多(这次是 120,927,000)。

两个结果之间的另一个明显区别是范围查询看起来似乎设法以tasks某种方式更有效地寻找。仅阅读49,440页面与78,137.

强制转换为日期版本所寻找的范围是从内部函数派生的GetRangeThroughConvert。该计划在 上显示了一个残差谓词CONVERT(date,[dbo].[tasks].[datetimecolumn],0)= [dbo].[events].[datecolumn]

如果查询 2 更改为

   LEFT OUTER JOIN tasks
     ON tasks.datetimecolumn > DATEADD(day, -1, events.datecolumn)
        AND tasks.datetimecolumn < DATEADD(day, 1, events.datecolumn)
Run Code Online (Sandbox Code Playgroud)

然后读取次数变得相同。该CAST AS DATE版本使用的动态查找读取不必要的行(两天而不是一天),然后用剩余谓词丢弃它们。

另一种可能性是重组表以将datetime组件存储在不同的列中。

CREATE TABLE [dbo].[tasks](
    [taskId] [int] IDENTITY(1,1) NOT NULL,
    [datecolumn] date NOT NULL,
    [timecolumn] time NOT NULL,
    [datetimecolumn]  AS DATEADD(day, DATEDIFF(DAY,0,[datecolumn]), CAST([timecolumn] AS DATETIME2(7))),
    [details] [char](1000) NULL,
PRIMARY KEY CLUSTERED 
(
    [datecolumn] ASC,
    [timecolumn] ASC
))
Run Code Online (Sandbox Code Playgroud)

datetimecolumn可以从组成部件衍生和这对行大小没有影响(如宽度date+time(n)是相同的宽度datetime2(n))。(一个例外是如果附加列增加了 的大小NULL_BITMAP

查询然后是一个直接的=谓词

SELECT events.eventId,
       MAX(tasks.details)
FROM   events
       LEFT OUTER JOIN tasks
         ON tasks.datecolumn = events.datecolumn
GROUP  BY events.eventId
Run Code Online (Sandbox Code Playgroud)

这将允许表之间的合并连接而无需任何排序。尽管对于这些表大小,还是选择了嵌套循环连接,统计信息如下。

Table 'tasks'. Scan count 5000, logical reads 44285
Table 'events'. Scan count 1, logical reads 717
   CPU time = 2980 ms,  elapsed time = 3012 ms.
Run Code Online (Sandbox Code Playgroud)

除了可能允许不同的逻辑连接类型date单独存储作为前导索引列之外,还可能有利于其他查询,tasks例如按日期分组。

至于为什么=谓词显示tasks> <=具有相同嵌套循环计划(44,285vs 49,440)的版本更少的逻辑读取,这似乎与预读机制有关。

打开跟踪标志652将范围版本的逻辑读取减少到与等于版本的逻辑读取相同。


归档时间:

查看次数:

33528 次

最近记录:

7 年,8 月 前