【问题标题】:Sql Server Delete and Merge performanceSql Server 删除和合并性能
【发布时间】:2011-11-29 09:57:55
【问题描述】:

我有一个包含一些买卖数据的表,其中包含大约 800 万条记录:

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。我已经创建了这样的存储过程,它采用表值参数:

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

对于包含 70k 条记录的表,此过程大约需要 7 秒。因此,使用 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]

【问题讨论】:

  • “大约 7 秒,表格有 70k 条记录” - 听起来不是特别快。你有什么索引?什么样的 I/O 子系统?
  • @MitchWheat 我用索引更新了问题。 I/O 子系统 - 如果您要求的话,有 2 个 ssd 驱动器 raid。我每晚都重建索引。
  • 是的。数据库服务器是 SQL Server 2008 R2。数据库以“简单”恢复模式运行。数据文件8.1GB,日志文件133MB
  • 哪个表有 70K 行?电视节目主持人?如果不是 TVP 中通常有多少行?另外你说哪个when not matched 是瓶颈?有两个..
  • @MartinSmith 我写的数字是关于事务表的。 DELETE/INSERT 我在有 8M 记录的表上进行了测试。 MERGE 我在最多 180k 条记录的表上进行了测试,结果非常糟糕,以至于我停止了。 TVP 大多是 1-10 条记录。很少有多达 100 条记录。

标签: sql-server tsql merge sql-delete


【解决方案1】:

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

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

这不是很明显,但 Microsoft 允许在执行合并之前过滤 TARGET 表(通过使用 CTE)。所以在我的例子中,目标行从 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;

【讨论】:

  • 那是一些严肃的忍者代码。我从来不知道你可以使用 CTE 作为 MERGE 目标。你刚刚将我的合并时间从两分钟多缩短到了大约 3 秒 - 干杯!
  • 是的,这行得通。在 MERGE 语句中使用过滤后的 CTE 作为目标表有助于避免在使用 NOT MATCHED BY SOURCE THEN DELETE 时对目标表进行聚集索引扫描操作。
  • 我敢肯定每个人都忘记了这一点,但我只是碰到了这个问题,这让我感到困惑:您可以在与源不匹配时为谓词提供一个 AND。但不知何故,它并没有像 CTE 那样解决性能问题。为什么?它们具有相同的含义 - 过滤我的目标空间。我是否遗漏了什么,或者 SQL Server 只是……不擅长这个?
【解决方案2】:

为 IsDeleted(或许多人所做的 IsActive)使用 BIT 字段是有效的,但它确实需要修改所有代码并创建单独的 SQL 作业以定期通过并删除“已删除”记录。这可能是要走的路,但首先要尝试一些不那么侵入性的方法。

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

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

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

EDIT2:

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

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

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

【讨论】:

  • 在您编写时更改索引似乎大大提高了性能并大大简化了执行计划。我正在测试它,很快就会写出结果:)
【解决方案3】:

看到这个https://stackoverflow.com/questions/3685141/how-to-....

更新与删除的成本是否相同?不,更新将是 更轻松的操作,特别是如果您在 PK 上有索引 (错误,这是一个 guid,而不是一个 int)。关键是更新到 位字段要便宜得多。 (大量)删除将强制 数据重新洗牌。

鉴于此信息,您使用位域的想法非常有效。

【讨论】:

    猜你喜欢
    • 2013-08-11
    • 2014-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-28
    • 1970-01-01
    相关资源
    最近更新 更多