【问题标题】:T-SQL query becomes very slow after large number of rows are deleted删除大量行后,T-SQL 查询变得很慢
【发布时间】:2021-07-08 20:47:23
【问题描述】:

如果我从表中插入然后删除大量行,则在表上运行的查询会变得非常慢。该表没有索引,但创建一个可以解决问题。但我不明白的是,为什么性能首先会下降。我还尝试在删除后更新表上的统计信息,但这并没有提高性能。有什么想法吗?

设置如下:

use tempdb;
go

drop table if exists dbo.MAvg;
CREATE TABLE dbo.MAvg
(id  INT   NOT NULL IDENTITY(1,1) ,
 val FLOAT NULL);
GO
INSERT INTO dbo.MAvg(val) VALUES
(1), (2), (3), (4), (1), (2), (3), (4), (1), (2);
GO

--A sample query which calculates moving average using a loop. I can do it using recursive CTE and cursor as well both of which will also show performance degradation
--It takes a few milliseconds to execute the query
declare @EMA_results as table(id int, val float, EMA float)
insert into @EMA_results
select top (1) id, val, val as EMA
from MAvg 
order by id asc

while @@ROWCOUNT>0
begin
insert into @EMA_results(id, val, EMA)
    select nxt.id, nxt.val, round((.7*nxt.val + .3*prev.EMA),2) as EMA 
    from MAvg as nxt inner join @EMA_results as prev
    on prev.id + 1 = nxt.id and prev.id = (select max(id) from @EMA_results)
end

select * 
from @EMA_results

--Now insert 1000000 rows. 
delete from MAvg;
DBCC CHECKIDENT ('MAvg', RESEED, 0);
;with cte(n) as
(
select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 10
)
insert into MAvg(val)
    select ABS(CHECKSUM(NEWID())%6)+1
        from cte as num1
        cross join cte as num2
        cross join cte as num3
        cross join cte as num4
        cross join cte as num5
        cross join cte as num6;

--now delete these rows and insert the 10 original rows
delete from MAvg;
DBCC CHECKIDENT ('MAvg', RESEED, 0);
INSERT INTO dbo.MAvg(val) VALUES
(1), (2), (3), (4), (1), (2), (3), (4), (1), (2);

--After deleting large number of rows, the same query takes 13 seconds to execute
declare @EMA_results as table(id int, val float, EMA float)
insert into @EMA_results
select top (1) id, val, val as EMA
from MAvg 
order by id asc

while @@ROWCOUNT>0
begin
insert into @EMA_results(id, val, EMA)
    select nxt.id, nxt.val, round((.7*nxt.val + .3*prev.EMA),2) as EMA 
    from MAvg as nxt inner join @EMA_results as prev
    on prev.id + 1 = nxt.id and prev.id = (select max(id) from @EMA_results)
end

select * 
from @EMA_results

【问题讨论】:

  • 我并不想避免创建索引。我正在尝试了解这种行为背后的“原因”。
  • 不完全——没有聚集索引的表是一个堆;只有非聚集索引的表也是一个堆。

标签: sql sql-server tsql indexing


【解决方案1】:

差异是由于对MAvg 执行的逻辑读取。

最初,表的 IO 统计信息是

Scan Count  Logical Reads   
        31  86

sp_spaceused shows rows 10, reserved 72KB

select * from MAvg 显示逻辑读取=1

删除行重新插入后,统计显示

Scan Count  Logical Reads   
        31  145,518

sp_spaceused shows rows 10, reserved 192KB

select * from MAvg 现在显示逻辑读取数=16

这很可能是因为仅仅删除行并不能释放表现在占用的pages,而表是heap

如果你truncate而不是删除问题不存在。

如果你创建一个唯一的聚集索引

create unique clustered index Idx_Id on mavg(id) with(fillfactor=100)

这个问题也不存在,因为重新插入的行都存在于一个页面上。

【讨论】:

  • 是的,堆中的页面没有被“垃圾收集”。删除前后的“总页数”和“总使用页数”分别为 2608 和 2606。我假设“总使用页数”会下降。但不是。奇怪!
  • 这里提到docs.microsoft.com/en-us/sql/t-sql/statements/…,除了截断之外,我还可以使用带有TABLOCK提示的DELETE来释放页面
  • 知道这一点也很有用。
  • @umbersar 在堆中很常见有鬼行。您应该始终拥有聚集索引的原因之一
猜你喜欢
  • 2023-03-13
  • 2014-03-18
  • 2014-01-21
  • 2019-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-20
  • 2014-02-09
相关资源
最近更新 更多