Sql Server删除和合并性能

时间:2011-10-02 13:48:03

标签: sql-server tsql merge sql-delete

我的表中包含一些买入/卖出数据,其中包含约8M的记录:

CREATE TABLE [dbo].[Transactions](
[id] [int] IDENTITY(1,1) NOT NULL,
[itemId] [bigint] NOT NULL,
[dt] [datetime] NOT NULL,
[count] [int] NOT NULL,
[price] [float] NOT NULL,
[platform] [char](1) NOT NULL
) ON [PRIMARY]

每个X分钟我的程序为每个itemId获取新的事务,我需要更新它。我的第一个解决方案是两步DELETE + INSERT:

delete from Transactions where platform=@platform and itemid=@itemid
insert into Transactions (platform,itemid,dt,count,price) values (@platform,@itemid,@dt,@count,@price)
[...]
insert into Transactions (platform,itemid,dt,count,price) values (@platform,@itemid,@dt,@count,@price)

问题是,这个DELETE语句平均需要5秒。这太长了。

我找到的第二个解决方案是使用MERGE。我创建了这样的存储过程,wchich采用表值参数:

CREATE PROCEDURE [dbo].[sp_updateTransactions]
@Table dbo.tp_Transactions readonly,
@itemId bigint,
@platform char(1)
AS
BEGIN
MERGE Transactions AS TARGET
USING @Table AS SOURCE  
ON (    
TARGET.[itemId] = SOURCE.[itemId] AND
TARGET.[platform] = SOURCE.[platform] AND 
TARGET.[dt] = SOURCE.[dt] AND 
TARGET.[count] = SOURCE.[count] AND
TARGET.[price] = SOURCE.[price] ) 


WHEN NOT MATCHED BY TARGET THEN 
INSERT VALUES (SOURCE.[itemId], 
                SOURCE.[dt],
                SOURCE.[count],
                SOURCE.[price],
                SOURCE.[platform])

WHEN NOT MATCHED BY SOURCE AND TARGET.[itemId] = @itemId AND TARGET.[platform] = @platform THEN 
DELETE;

END

此过程大约需要7秒,表格中有70k记录。所以8M可能需要几分钟。瓶颈是“当不匹配”时 - 当我评论这一行时,这个程序平均运行0,01秒。

所以问题是:如何提高删除语句的性能?

需要删除才能确保该表不包含已在应用程序中删除的事务。但真正的情况是它很少发生,在10000次交易更新中,删除记录的真正需求小于1。

我的理论解决方法是创建额外的列,例如“transactionDeleted bit”并使用UPDATE而不是DELETE,然后每隔X分钟或几小时通过批处理作业清理表并执行

delete from transactions where transactionDeleted=1

它应该更快,但我需要更新应用程序其他部分的所有SELECT语句,只使用transactionDeleted = 0记录,因此它也可能会影响应用程序性能。

你知道更好的解决方案吗?

更新:当前索引:

CREATE NONCLUSTERED INDEX [IX1] ON [dbo].[Transactions] 
(
[platform] ASC,
[ItemId] ASC
) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF,   IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 50) ON [PRIMARY]


CONSTRAINT [IX2] UNIQUE NONCLUSTERED 
(
[ItemId] DESC,
[count] ASC,
[dt] DESC,
[platform] ASC,
[price] ASC
) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

3 个答案:

答案 0 :(得分:5)

好的,这也是另一种方法。对于类似的问题(大型扫描,如果没有匹配源然后删除)我将MERGE执行时间从806ms减少到6ms!

上述问题的一个问题是“WHEN NOT MATCHED BY SOURCE”子句正在扫描整个TARGET表。

这并不是那么明显,但微软允许在进行合并之前过滤(通过使用CTE)TARGET表。所以在我的情况下,TARGET行从250K减少到不到10行。差异很大。

假设上述问题适用于由@itemid和@platform过滤的TARGET,那么MERGE代码将如下所示。上面对索引的更改也有助于这个逻辑。

WITH Transactions_CTE (itemId
                        ,dt
                        ,count
                        ,price
                        ,platform
                        )
AS
-- Define the CTE query that will reduce the size of the TARGET table.  
(  
    SELECT itemId
        ,dt
        ,count
        ,price
        ,platform
    FROM Transactions  
    WHERE itemId = @itemId
      AND platform = @platform  
)  
MERGE Transactions_CTE AS TARGET
USING @Table AS SOURCE
    ON (
        TARGET.[itemId] = SOURCE.[itemId]
        AND TARGET.[platform] = SOURCE.[platform]
        AND TARGET.[dt] = SOURCE.[dt]
        AND TARGET.[count] = SOURCE.[count]
        AND TARGET.[price] = SOURCE.[price]
        )
WHEN NOT MATCHED BY TARGET  THEN
        INSERT
        VALUES (
            SOURCE.[itemId]
            ,SOURCE.[dt]
            ,SOURCE.[count]
            ,SOURCE.[price]
            ,SOURCE.[platform]
            )
WHEN NOT MATCHED BY SOURCE THEN
        DELETE;

答案 1 :(得分:2)

对IsDeleted(或许多人这样做的IsActive)使用BIT字段是有效的,但它确实需要修改所有代码并创建一个单独的SQL作业来定期通过并删除“已删除”记录。这可能是要走的路,但首先尝试的东西不那么干扰。

我在你的2个索引中注意到它们都不是CLUSTERED。我可以假设IDENTITY字段是?您可以考虑将[IX2] UNIQUE索引设置为CLUSTERED并更改PK(再次,我假设IDENTITY字段是CLUSTERED PK)为NONCLUSTERED。我还会重新排序IX2字段以将[Platform]和[ItemID]放在第一位。由于您的主要操作是将[Platform]和[ItemID]作为一个集合进行查找,因此以这种方式对它们进行物理排序可能会有所帮助。由于这个索引是独一无二的,因此它是CLUSTERED的一个很好的候选者。它当然值得测试,因为这将影响对该表的所有查询。

此外,如果按照我的建议更改索引会有所帮助,那么仍然可能值得尝试这两个想法,因此也要进行IsDeleted字段以查看是否会进一步提高性能。

编辑: 我忘了提到,通过使IX2索引CLUSTERED并将[Platform]字段移到顶部,你应该摆脱IX1索引。

EDIT2:

为了清楚起见,我建议的事情如下:

CREATE UNIQUE CLUSTERED  INDEX [IX2]
(
[ItemId] DESC,
[platform] ASC,
[count] ASC,
[dt] DESC,
[price] ASC
)

公平地说,改变哪个索引是CLUSTERED也会对在[id]字段上完成JOIN的查询产生负面影响,这就是你需要彻底测试的原因。最后,您需要针对最常见和/或昂贵的查询调整系统,并且可能不得不接受某些查询会因此而变慢,但这可能比此操作更快。

答案 2 :(得分:0)

请参阅此https://stackoverflow.com/questions/3685141/how-to-....

  

更新是否与删除成本相同?不会。更新将是   一个更轻松的操作,特别是如果你有PK的索引   (错误,这是一个guid,而不是int)。重点是更新   比特场要便宜得多。 (大规模)删除会强制a   重新洗牌数据。

根据这些信息,您使用位字段的想法非常有效。