【问题标题】:In SQL, is UPDATE always faster than DELETE+INSERT?在 SQL 中,UPDATE 总是比 DELETE+INSERT 快吗?
【发布时间】:2020-01-03 22:59:07
【问题描述】:

假设我有一个包含以下字段的简单表:

  1. ID:int、自增(身份)、主键
  2. 名称:varchar(50),唯一,具有唯一索引
  3. 标签:int

我从不使用 ID 字段进行查找,因为我的应用程序始终基于使用 Name 字段。

我需要不时更改标签值。我正在使用以下简单的 SQL 代码:

UPDATE Table SET Tag = XX WHERE Name = YY;

我想知道是否有人知道上述是否总是比:

DELETE FROM Table WHERE Name = YY;
INSERT INTO Table (Name, Tag) VALUES (YY, XX);

再一次 - 我知道在第二个示例中,ID 已更改,但这对我的应用程序无关紧要。

【问题讨论】:

  • 作为一种习惯,我绝不建议更新主键列。
  • @KM:我同意,这是对我的真实表的简化,其中所有查找都是在一个不是主键的唯一字符串字段上完成的。我确实有一个完全不相关的主键 int 值,所以我从示例中删除了它(它是自动创建的,根本不参与查找)
  • @KM:我更新了示例以反映真实的表结构,以防万一它有所作为。
  • UDPATE 还具有不破坏表可能具有的任何外键关系的好处,只要被引用的键字段不改变。如果你 DELETE + INSERT,你的一些约束可能会被违反,因此 DELETE 可能会失败

标签: sql sql-insert sql-delete


【解决方案1】:

这个答案有点太晚了,但是因为我遇到了类似的问题,所以我在我使用过的同一台机器上用 JMeter 和 MySQL 服务器进行了测试:

  1. 包含两个 JDBC 请求的事务控制器(生成父样本):删除和插入语句
  2. 包含更新语句的单独 JDBC 请求。

在运行了500个循环的测试后,我得到了以下结果:

DEL + INSERT - 平均:62 毫秒

更新 - 平均:30 毫秒

结果:

【讨论】:

  • 我很想看到具有大型数据集和索引的相同统计数据。
  • 嗨,Michael。该测试是在 5 年前针对本地主机(在不再存在的机器上)执行的。为了获得一定的准确性,应该在相同的表和索引表上重复测试。最初的想法只是抓住性能差异的味道。如果时间允许,我可能会重复并更新帖子。我很高兴这篇文章仍然激发了人们的好奇心:)
【解决方案2】:

表越大(列的数量和大小),删除和插入而不是更新的成本就越高。因为你要付出UNDO和REDO的代价。 DELETE 比 UPDATE 占用更多的 UNDO 空间,并且您的 REDO 包含的语句数量是必要的两倍。

此外,从商业角度来看,这是完全错误的。考虑一下要理解该表上的名义审计跟踪会有多困难。


在某些情况下,涉及批量更新表中的所有行,使用 CTAS 从旧表创建新表(在 SELECT 子句的投影中应用更新)、删除旧表会更快并重命名新表。副作用是创建索引、管理约束和更新权限,但值得考虑。

【讨论】:

    【解决方案3】:

    同一行上的一个命令应该总是比同一行上的两个命令快。所以只更新会更好。

    编辑 摆好桌子:

    create table YourTable
    (YourName  varchar(50)  primary key
    ,Tag int
    )
    
    insert into YourTable values ('first value',1)
    

    运行这个,在我的系统(sql server 2005)上需要 1 秒:

    SET NOCOUNT ON
    declare @x int
    declare @y int
    select @x=0,@y=0
    UPDATE YourTable set YourName='new name'
    while @x<10000
    begin
        Set @x=@x+1
        update YourTable set YourName='new name' where YourName='new name'
        SET @y=@y+@@ROWCOUNT
    end
    print @y
    

    运行这个,在我的系统上花了 2 秒:

    SET NOCOUNT ON
    declare @x int
    declare @y int
    select @x=0,@y=0
    while @x<10000
    begin
        Set @x=@x+1
        DELETE YourTable WHERE YourName='new name'
        insert into YourTable values ('new name',1)
        SET @y=@y+@@ROWCOUNT
    end
    print @y
    

    【讨论】:

    • 你是根据任何具体数据做的吗?
    • @Rax Olgud,你怎么回答这个问题?你甚至没有说你正在使用什么数据库。您问了一个概念性问题,但想要具体数据。如果您想要实际数据,那么您将需要编写一个诡计循环(在您的系统上),并更新该行 1000 次,编写另一个循环将其删除/插入 1000 次。看看哪个更快。
    • @Rax Olgud,在删除和创建索引值以及检查任何约束方面存在一些开销。如果您只是更新数据列,它将避免任何此类开销。
    • @Mohy66,测量的是运行时间,行数的总和是为了验证测试期间完成的工作量。感谢您的反对。
    【解决方案4】:

    恐怕您问题的主体与标题问题无关。

    如答题:

    在 SQL 中,UPDATE 总是比 DELETE+INSERT 快吗?

    那么答案是否定的!

    谷歌搜索

    • “昂贵的直接更新”*“sql server”
    • “延迟更新”*“sql server”

    与直接插入+更新相比,此类更新导致通过插入+更新实现更新的成本更高(更多处理)。

    • 使用唯一(或主)键或
    • 更新字段
    • 当新数据不适合(更大)更新前分配的行空间(甚至最大行大小),导致碎片,
    • 等。

    我的快速(非详尽)搜索,不是假装覆盖一个,给了我 [1],[2]

    [1]
    更新操作
    (Sybase® SQL Server 性能和调优指南
    第 7 章:SQL Server 查询优化器)
    http://www.lcard.ru/~nail/sybase/perf/11500.htm
    [2]
    UPDATE 语句可以复制为 DELETE/INSERT 对
    http://support.microsoft.com/kb/238254

    【讨论】:

      【解决方案5】:

      请记住,与正确实施的 UPDATE 相对的 DELETE+INSERT 发出时发生的实际碎片会随着时间的推移而产生很大差异。

      这就是为什么不鼓励使用 MySQL 实现的 REPLACE INTO 而不是使用 INSERT INTO ... ON DUPLICATE KEY UPDATE ... 语法的原因。

      【讨论】:

        【解决方案6】:

        刚刚尝试在一个有 44 个字段的表上更新 43 个字段,剩下的字段是主聚集键。

        更新耗时 8 秒。

        Delete + Insert 比“Client Statistics”通过 SQL Management Studio 报告的最小时间间隔更快。

        彼得

        MS SQL 2008

        【讨论】:

          【解决方案7】:

          在你的情况下,我相信更新会更快。

          记住索引!

          你已经定义了一个主键,它很可能会自动变成一个聚集索引(至少 SQL Server 是这样)。集群索引意味着记录根据索引物理放置在磁盘上。 DELETE 操作本身不会造成太大的麻烦,即使在一条记录消失后,索引仍然保持正确。但是当您插入一条新记录时,数据库引擎将不得不将此记录放在正确的位置,这在某些情况下会导致旧记录的一些“重新洗牌”为新记录“腾出位置”。在那里它会减慢操作速度。

          如果值不断增加,则索引(尤其是集群)效果最好,因此新记录只会附加到尾部。也许您可以添加一个额外的 INT IDENTITY 列成为聚集索引,这将简化插入操作。

          【讨论】:

          • “改组”会有页面拆分。
          • 在此示例中,新记录将位于表的末尾(基于 PK),因为用户未指定 PK。如果“名称”索引是聚集的,那么这将是一个问题,但它不太可能是聚集的。
          【解决方案8】:

          如果没有特定的速度问题,速度问题就无关紧要了。

          如果您正在编写 SQL 代码来更改现有行,请更新它。其他都不对。

          如果你要打破代码应该如何工作的规则,那么你最好有一个很好的量化理由,而不是“这种方式更快”的模糊想法,当你不这样做时'不知道什么是“更快”。

          【讨论】:

          • 你是对的,对你的回答做一个小注释。如果表有触发器,delete+insert不好选。
          【解决方案9】:

          如果您有几百万行怎么办。每行都以一条数据开始,也许是一个客户名称。当您为客户收集数据时,必须更新他们的条目。现在,让我们假设客户端数据的集合分布在许多其他机器上,然后从这些机器中收集并放入数据库中。如果每个客户端都有唯一的信息,那么您将无法执行批量更新;即,没有可用于一次性更新多个客户端的 where 子句标准。另一方面,您可以执行批量插入。因此,问题可能更好地提出如下:执行数百万次更新是否更好,或者将它们编译成大批量删除和插入更好。换句话说,不是“更新 [table] set field=data where clientid=123”一百万次,而是从 [table] where clientid in ([all clients to be updated]) 中删除;插入 [table]值(client1 的数据)、(client2 的数据)等'

          是哪个选择都比另一个更好,还是你都搞砸了?

          【讨论】:

          • (已被作者删除)
          • 在下面查看我的相关回答。
          【解决方案10】:

          显然,答案因您使用的数据库而异,但 UPDATE 总是可以比 DELETE+INSERT 更快地实现。由于内存中的操作无论如何都是微不足道的,给定一个基于硬盘驱动器的数据库,更新可以在硬盘上就地更改数据库字段,而删除将删除一行(留下一个空白空间),并插入一个新的行,可能到表的末尾(同样,这一切都在实现中)。

          另一个小问题是,当您更新单行中的单个变量时,该行中的其他列保持不变。如果您 DELETE 然后执行 INSERT,则可能会忘记其他列并因此留下它们(在这种情况下,您必须在 DELETE 之前执行 SELECT 以临时存储其他列,然后再使用 INSERT 将它们写回) .

          【讨论】:

          • 我不确定我是否同意您的第一点,尤其是在使用可变长度字符串类型时。更新这些可能确实需要在“新地方”进行高清写入。
          【解决方案11】:

          删除 + 插入几乎总是更快,因为更新涉及更多步骤。

          更新:

          1. 使用 PK 查找行。
          2. 从磁盘读取行。
          3. 检查哪些值发生了变化
          4. 使用填充的 :NEW 和 :OLD 变量引发 onUpdate 触发器
          5. 将新变量写入磁盘(整行)

            (对于您要更新的每一行都会重复此操作)

          删除+插入:

          1. 将行标记为已删除(仅在 PK 中)。
          2. 在表格末尾插入新行。
          3. 使用新记录的位置更新 PK 索引。

            (这里不再重复,都可以在一个单独的操作块中执行)。

          使用 Insert + Delete 会分散您的文件系统,但速度不会那么快。在后台进行惰性优化将始终释放未使用的块并完全打包表格。

          【讨论】:

          • 这个答案过度简化了操作,并且遗漏了主要商业 RDBM 模型的许多步骤 - 仅通过更改 PK(仅此而已)删除一行并不是主要商业 RDBM 的工作方式.您关于触发器的信息不正确且片面。首先,删除/插入可以/也可以触发触发器 - 但您没有包含这些触发器。除非您指定每行触发器,否则它也只会为更新触发一次,为删除/插入触发两次。
          【解决方案12】:

          这取决于产品。可以实现一个产品,它(在幕后)将所有 UPDATE 转换为(事务性包装的)DELETE 和 INSERT。前提是结果与 UPDATE 语义一致。

          我并不是说我知道有任何产品这样做,但这是完全合法的。

          【讨论】:

          • ... 完全合法,只要将外键约束检查推迟到插入之后,这可能是不合法的。
          • 我不确定,但我听说 SQL Server 在内部为 UPDATE 执行了 DELETE+INSERT。如果是这样的话,在 SQL Server 的情况下会有什么不同吗?
          • @Faiz - 与所有事情一样,唯一确定的方法是在您的环境中使用您的数据进行测试。这些操作的潜在成本不太可能成为您的瓶颈 - 永远。使用 SQL Server,如果您有触发器,它肯定类似于 删除/插入,但系统是否实际执行此操作,谁需要知道 :-)
          【解决方案13】:

          每次写入数据库都有很多潜在的副作用。

          删除:必须删除一行,更新索引,检查外键并可能级联删除等。 插入:必须分配一行——这可能代替删除的行,也可能不是;必须更新索引,检查外键等。 更新:必须更新一个或多个值;也许该行的数据不再适合数据库的该块,因此必须分配更多空间,这可能会级联成多个正在重写的块,或者导致碎片块;如果该值具有外键约束,则必须对其进行检查,等等。

          对于极少数列或整行更新删除+插入可能会更快,但 FK 约束问题是一个大问题。当然,也许您现在没有 FK 限制,但这是否总是正确的?如果你有一个触发器,如果​​更新操作是真正的更新,那么编写处理更新的代码会更容易。

          另一个需要考虑的问题是,有时插入和删除所持有的锁与更新所持有的锁不同。数据库可能会在您插入或删除时锁定整个表,而不是在您更新该记录时锁定一条记录。

          最后,如果您要更新记录,我建议您只更新它。然后检查您的数据库的性能统计信息和该表的统计信息,看看是否有性能改进。其他任何事情都为时过早。

          我从事的电子商务系统的一个示例:我们通过两步方法将信用卡交易数据存储在数据库中:首先,编写部分交易以表明我们已经开始了该流程。然后,当授权数据从银行返回时更新记录。我们可以删除然后重新插入记录,但我们只是使用更新。我们的 DBA 告诉我们,表是碎片化的,因为 DB 只为每一行分配少量空间,并且更新导致了块链,因为它添加了很多数据。但是,我们没有切换到 DELETE+INSERT,而是将数据库调整为始终分配整行,这意味着更新可以毫无问题地使用预先分配的空白空间。无需更改代码,代码简单易懂。

          【讨论】:

            【解决方案14】:

            在特定情况下,删除+插入可以节省您的时间。我有一个包含 30000 奇数行的表,并且使用数据文件每天更新​​/插入这些记录。上传过程生成 95% 的更新语句,因为记录已经存在,5% 的插入语句用于不存在的记录。或者,将数据文件记录上传到临时表中,删除临时表中记录的目标表,然后从临时表中插入相同的记录,这样可以节省 50% 的时间。

            【讨论】:

              【解决方案15】:

              大量单独更新与批量删除/批量插入是我的场景。我有多个客户多年前的历史销售数据。在我获得验证数据(下个月 15 日)之前,我将每天调整销售数字以反映从另一个来源获得的当前状态(这意味着每个客户每天最多覆盖 45 天的销售)。可能没有变化,也可能有一些变化。我可以编写逻辑代码以查找差异并更新/删除/插入受影响的记录,或者我可以删除昨天的数字并插入今天的数字。显然,后一种方法更简单,但如果它会因流失而破坏表的性能,那么编写额外的逻辑来识别少数(或没有)更改的记录并仅更新/删除/插入这些记录是值得的。

              所以,我正在替换记录,并且旧记录和新记录之间可能存在某种关系,但总的来说我不一定希望将旧数据与新数据匹配(那将是额外的步骤,并会导致删除、更新和插入)。此外,更改的字段相对较少(最多 20 个字段中的 7 个或 15 个字段中的 2 个)。

              可能一起检索的记录将同时插入,因此物理上应该彼此靠近。这是否弥补了由于该方法的流失而导致的性能损失,它是否比所有这些单独记录更新的撤消/重做成本更好?

              【讨论】:

                猜你喜欢
                • 2011-05-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-12-19
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-10-25
                相关资源
                最近更新 更多