【问题标题】:How to optimise this TSQL如何优化 SQL
【发布时间】:2023-03-05 23:30:02
【问题描述】:

这个查询大约需要01:30 来运行:

select DATEADD(dd, 0, DATEDIFF(dd, 0, t1.[OccurredOn]))
       , count(t2.UserId)
       , count(*) - count(t2.UserId)
from Events t1
left join (select c.UserId, min(c.OccurredOn) FirstOccurred
           from Events c
           where [OccurredOn] between @start and @end
           group by c.UserId) t2 on t1.OccurredOn = t2.FirstOccurred and t1.UserId = t2.UserId
where t1.EventType = @eventType
    and t1.[OccurredOn] between @start and @end
group by DATEADD(dd, 0, DATEDIFF(dd, 0, t1.[OccurredOn]))
order by DATEADD(dd, 0, DATEDIFF(dd, 0, t1.[OccurredOn]))

如果我从子查询中删除 WHERE 子句,它会立即运行。

单独运行子查询,WHERE 需要

如果我首先将子查询SELECT 放入表变量中,然后加入该变量,则整个查询将在 19 秒内运行。

Events 表如下所示:

[Events](
    [EventType] [uniqueidentifier] NOT NULL,
    [UserId] [uniqueidentifier] NOT NULL,
    [OccurredOn] [datetime] NOT NULL,
)

我有以下primary, nonclustered, nounique 索引:

  • 事件类型
  • 用户ID
  • 发生时间

这是执行计划

我使用的是 SQL Server 2008。

两件事:

  1. 发生了什么让这变慢了?
  2. 如何加快速度?

【问题讨论】:

  • SHOW US表结构,列和它们的数据类型,告诉我们你有什么索引...... - 还有什么VERSION的SQL Server你在用吗?
  • 好点,意味着添加添加。待机
  • 你看过执行计划了吗?它显示了什么?
  • 您确定您的查询有效吗?您的子查询返回一个名为FirstOccured 的列,这在技术上是一个谎言,因为where 子句意味着它是@start 之后的第一个。此外,人们会期望 FirstOccurred 是事件的第一次发生。但事实并非如此。所以,如果你的子查询已经误导了它应该做什么 - 你怎么能确定其余的都是正确的?也许最好解释一下查询应该做什么 - 我怀疑完全重写是为了,而不仅仅是一些优化。
  • 我告诉过您,您在编写查询时遇到了一些严重的问题。我还可以告诉您,您几乎肯定有错误的索引,因为您对索引的解释没有任何意义(不唯一的主索引??多个主索引??)。关键是,如果您懒得解释您的查询应该做什么 - 如何期望有人帮助您重写它并解决您的问题? - 这不是一个可以回答的问题!

标签: sql tsql optimization


【解决方案1】:

您的查询速度很慢,因为您的排序取决于动态计算(DATEADD(dd, 0, DATEDIFF(dd, 0, t1.[OccurredOn]))),Sql Server 无法在动态计算中使用索引。

Postgresql 有index on expression,使用 Postgresql,您基本上可以为您将表达式的结果保存到实际列(幕后列),所以到时候您需要对该表达式进行排序,Postgresql可以在该表达式上使用索引。

Sql Server 中最接近的类似特性是持久化公式。

您可以通过此示例查询轻松验证该功能:

create table PersonX
(
Lastname varchar(50) not null,
Firstname varchar(50) not null
);

create table PersonY
(
Lastname varchar(50) not null,
Firstname varchar(50) not null
);


alter table PersonX add Fullname as Lastname + ', ' + Firstname PERSISTED;    
create index ix_PersonX on PersonX(Fullname);

declare @i int = 0;

while @i < 10000 begin
    insert into PersonX(Lastname,Firstname) values('Lennon','John');
    insert into PersonY(Lastname,Firstname) values('Lennon','John');
    set @i = @i + 1;
end;


select top 1000 Lastname, Firstname
from PersonX
order by Fullname;


select top 1000 Lastname, Firstname
from PersonY
order by Lastname + ', ' + Firstname;

在 PersonX 上对全名下订单比 PersonY 快。 PersonX 的查询成本仅为 32%,而 PersonY 为 68%

要解决查询的性能问题,请执行以下操作:

alter table Events 
    add OccurenceGroup as 
        DATEADD(dd, 0, DATEDIFF(dd, 0, [OccurredOn])) PERSISTED

create index ix_Events on Events(OccurenceGroup);

然后在 OccurenceGroup 上进行分组和排序。


顺便问一下,您是否在 OccuredOn 和 EventType 上添加了索引?

【讨论】:

    【解决方案2】:

    您可以尝试将LEFT JOIN 替换为LEFT MERGE JOIN,这样派生表t2 只计算一次,而不是为每个用户重新计算MIN 可能多次。

    您也可以使用如下的排名函数重写它。它可能更便宜。您需要根据您的数据和索引来测试这些想法。

    ;WITH T AS
    (
    SELECT *,
           RANK() OVER (PARTITION BY UserId ORDER BY OccurredOn) AS Rnk
    FROM Events
    WHERE [OccurredOn] BETWEEN @start AND @end
    )
    SELECT Dateadd(dd, 0, Datediff(dd, 0, OccurredOn)),
           COUNT(CASE WHEN Rnk =1 THEN 1 END),
           COUNT(CASE WHEN Rnk >1 THEN 1 END)
    FROM T
    WHERE EventType = @eventType      
    GROUP BY Dateadd(dd, 0, Datediff(dd, 0, OccurredOn)) 
    ORDER BY Dateadd(dd, 0, Datediff(dd, 0, OccurredOn)) 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-31
      • 2017-12-19
      相关资源
      最近更新 更多