许多行的最高效(快速)T-SQL DELETE?

时间:2009-04-03 16:03:16

标签: sql-server performance tsql

我们的服务器应用程序每天以1000-2000行的速度接收有关要添加到数据库的行的信息。表中有两个互斥的列,用于唯一标识一行:一个是名为“tag”的数字标识符,另一个是名为“longTag”的50字符串。一行可以有标签或longTag;不是两个。

从套接字进入的每一行可能已经存在,也可能不存在于表中。如果存在,则必须使用新信息更新该行。如果它不存在,则必须添加。我们使用SQL 2005,在少数情况下甚至使用SQL 2000,因此我们无法使用新的MERGE关键字。

我现在这样做的方法是构建一个巨大的DELETE语句,如下所示:

DELETE from MyRecords
WHERE tag = 1
OR tag = 2
OR longTag = 'LongTag1'
OR tag = 555

...每个传入的行都有自己的'OR tag = n'或'OR longTag ='x''子句。

然后我使用ISQLXMLBulkLoad执行XML批量加载,一次加载所有新记录。

巨大的DELETE声明有时会超时,需要30秒或更长时间。我不确定为什么。

当记录从套接字进入时,必须插入它们或者它们必须替换现有的行。我这样做是最好的方式吗?

编辑:新行与替换行的比率将非常倾向于新行。在我看到的生产数据中,每次修正通常会有100-1000个新行。

编辑2 :插入和删除都必须作为单个事务处理。如果插入或删除失败,它们必须都回滚,使表格处于插入和插入之前的状态。删除开始了。

编辑3 :关于NULL标签。我需要先简要介绍一下系统。这是一个交易系统的数据库。 MyTable是一个包含两种交易的交易表:所谓的“日间交易”和所谓的“开仓”。日间交易只是交易 - 如果您是期权交易者并且您进行了交易,那么该交易将是该系统中的日间交易。开盘头基本上是您的投资组合的摘要,直到今天。开仓头寸和日内交易都存储在同一个表格中。日间交易有标签(longTags或数字标签),而开仓位则没有。打开位置可能有重复的行 - 这很好&正常。但是,日交易不能有重复的行。如果日间交易与数据库中已存在的某个记录具有相同的标记,则表中的数据将替换为新数据。

因此tag和amp;中的值有4种可能性。 longTag:

1)标签非零& longTag为空:这是一个带有数字标识符的日间交易。 2)tag为零,longTag具有非空字符值。这是一个带有字母数字标识符的日间交易。 3)tag为零,longTag为空:这是一个开放位置。 4)标签非零,longTag具有非空字符值。这可以防止我们的服务器软件发生的每一件事,但如果它发生了,那么longTag将被忽略,它将被视为与案例#1相同。同样,这不会发生。

8 个答案:

答案 0 :(得分:5)

我认为将巨大的DELETE语句拆分为2 DELETE可能有所帮助。

1 DELETE处理标记和单独的DELETE来处理longTag。 这将有助于SQL服务器有效地选择使用索引。

当然,您仍然可以在1个DB往返中触发2个DELETE语句。

希望这有帮助

答案 1 :(得分:4)

OR(或in)几乎与每个OR操作数是不同的查询一样工作。也就是说,它变成了表扫描,对于每一行,数据库必须将每个OR操作数作为谓词进行测试,直到找到匹配或用尽操作数。

将其打包的唯一理由是使其成为一个合乎逻辑的工作单元。您还可以在事务中包装一堆删除,并且只在所有删除成功完成时才提交。

Quassnoi提出了一个有趣的建议 - 使用表格 - 但是因为他然后使用IN和OR,它就会出现相同的结果。

但试试这个。

创建一个镜像真实表的新表。称之为u_real_table。在tag和longTag上对其进行索引。

将所有传入数据放入u_real_table。

现在,当您准备好做大量的事情时,请将镜像表加到标签上的真实表中。从实际表中删除u_real_table中的所有标记行:

delete real_table from real_table a 
   join u_real_table b on (a.tag = b.tag);
insert into real_table select * 
   from u_real_table where tag is not null;

看看我们在这做了什么?由于我们仅在标记上加入,因此标记索引的使用可能性更大。

首先我们删除了所有新内容,然后我们插入了新的替代品。我们也可以在这里做更新。哪个更快取决于您的表结构及其索引。

我们没有编写脚本来完成它,我们只需要在u_real_table中插入记录。

现在我们为longTags做同样的事情:

delete real_table from real_table a 
   join u_real_table b on (a.longTag = b.longTag);
insert into real_table select * 
   from u_real_table where longTag is not null;

最后,我们清除了u_real_table:

delete from u_real_table;

显然,我们将每个删除/插入对包装在一个事务中,这样只有在后续插入成功时才删除,然后我们将整个事件包装在另一个事务中。因为它是一个合乎逻辑的工作单元。

此方法可减少您的手动操作,减少手动错误的可能性,并且有可能加快删除速度。

请注意,这依赖于缺少的标签和longTags正确为空,而不是零或空字符串。

答案 2 :(得分:3)

也许:

DELETE FROM MyRecords
WHERE  tag IN (1, 2, 555) -- build a list
OR longTag IN ('LongTag1')

我怀疑索引会帮助你删除但是会大大减慢你的插入,所以我不会那么玩。但是后来我的直觉还不是很完美,你可能能够调整FillFactor或其他项目以解决这个问题,而且我确实知道你确实想要描述它们的一件事。

另一种选择是将新插入加载到临时表(名为InputQueue),然后加入MyRecords上的临时表来处理过滤更新。这也可以通过两个步骤轻松完成更新:您可以将Tags和longTags删除为单独的操作,这可能会更有效。

答案 3 :(得分:3)

这样的事情可以简化流程(你只需要插入行,无论它们是否已经存在 - 不需要前面的DELETE语句):

CREATE TRIGGER dbo.TR_MyTable_Merge 
   ON  dbo.MyTable 
   INSTEAD OF INSERT
AS 
BEGIN
  SET NOCOUNT ON;

  BEGIN TRANSACTION

  DELETE MyTable 
  FROM   MyTable t INNER JOIN inserted i ON t.tag = i.tag 

  DELETE MyTable 
  FROM   MyTable t INNER JOIN inserted i ON t.longTag = i.longTag

  INSERT MyTable 
  SELECT * FROM inserted

  COMMIT TRANSACTION

  SET NOCOUNT OFF;
END

编辑:以前将DELETE语句组合成两个单独的语句,以实现最佳索引使用。

根本不使用DELETE,而是在索引上更容易更新受影响/重复的行。

答案 4 :(得分:3)

观看此视频,了解如何进行“啃咬”删除。这个过程运作良好,绝对可以减少您所看到的锁定/碰撞问题:

http://www.sqlservervideos.com/video/nibbling-deletes

答案 5 :(得分:2)

似乎您的表未在(tag)(longTag)

上建立索引

构建两个索引:一个在(tag),一个在(longTag)

如果您打算删除大量的记录,那么声明两个表变量,用值填充它们并删除如下:

DECLARE @tag TABLE (id INT);
DECLARE @longTag TABLE (id VARCHAR(50));

INSERT
INTO  @tag
VALUES (`tag1`)

INSERT
INTO  @tag
VALUES (`tag2`)

/* ... */

INSERT INTO @longTag
VALUES ('LongTag1')

/* ... */


DELETE
FROM    MyRecords r
WHERE   r.tag IN (SELECT * FROM @tag)
        OR r.longTag IN (SELECT * FROM @longTag)

您也可以尝试执行两遍DELETE

DELETE
FROM    MyRecords r
WHERE   r.tag IN (SELECT * FROM @tag)

DELETE
FROM    MyRecords r
WHERE   r.longTag IN (SELECT * FROM @longTag)

并查看哪些语句运行时间更长,以查看索引是否存在问题。

答案 6 :(得分:2)

使用OR可能会导致表扫描 - 您可以将其分解为四个语句吗?在交易中包装每一个也可以加快速度。

DELETE from MyRecords
WHERE tag = 1

DELETE from MyRecords
WHERE tag = 2

DELETE from MyRecords
WHERE tag = 555

DELETE from MyRecords
WHERE longTag = 'LongTag1'

答案 7 :(得分:1)

<强>索引:

考虑为longTag使用索引持久计算列,该列存储longTag的校验和。您可以索引一个4字节的int值(86939596),而不是索引'LongTag1'。

然后你的查找[希望*]更快,你只需要在查询/删除中包含longTag值。您的代码会稍微复杂一些,但索引可能会更有效率。

*需要测试