AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 166833
Accepted
krystah
krystah
Asked: 2017-03-11 05:56:25 +0800 CST2017-03-11 05:56:25 +0800 CST 2017-03-11 05:56:25 +0800 CST

“空”更新是否会创建等量的事务日志记录?[复制]

  • 772
这个问题在这里已经有了答案:
当更新值相同时,数据库引擎是否更新?[关闭] (3 个答案)
5 年前关闭。

问题:在 SQL Server 2016 中,将列更新为相同的值(例如,将列从更新'john'为'john')是否会产生与将列更新为不同值时相同数量的事务日志记录?阅读下文了解更多详情。

我们有几个按计划运行的 SQL 代理作业。这些作业从源表(复制数据、链接服务器)中选择数据,对其进行转换,然后相应地插入/更新/删除本地目标表的行。

在尝试找到实现这一目标的最佳方法时,我们已经通过各种策略。我们试过了

  • 使用 MERGE 从源更新目标
  • 使用 UPDATE 从源更新目标以更新所有列
  • 使用每个目标列的单个 UPDATE 语句从源更新目标

现在,我只是一个初级 DBD,我对事务日志如何工作的理解非常有限。话虽如此,我的前辈们得出的结论是,我们不能使用 MERGE 或 UPDATE 语句,其中所有列都在同一语句中处理,因为它会产生过多的日志记录。这样做的理由是,当您UPDATE在 SQL Server 中执行语句时,当您设置一个列值并且新值等于旧值时,它仍然在事务日志中标记为更新。当您执行大量无意义的 SET 操作时,这显然会变得代价高昂。

在以下示例中,我们使用源表中的值更新目标表的first_name和,并由 连接。last_nameid

-- create source- and target-table
CREATE TABLE [#tgt] (
    [id] Int PRIMARY KEY,
    [first_name] NVarchar(10),
    [last_name] NVarchar(10)
)
CREATE TABLE [#src] (
    [id] Int PRIMARY KEY,
    [first_name] NVarchar(10),
    [last_name] NVarchar(10)
)

-- fill some dummy-data
INSERT INTO [#tgt]([id], [first_name], [last_name])VALUES(1, 'john', 'lennon')
INSERT INTO [#src]([id], [first_name], [last_name])VALUES(1, 'john', 'cena')

-- update target-table with values from source-table
UPDATE 
    [T]
SET 
    [T].[first_name] = [S].[first_name],
    [T].[last_name] = [S].[last_name]
FROM
    [#tgt] AS [T]
    JOIN [#src] AS [S] ON [S].[id] = [T].[id]

DROP TABLE [#tgt]
DROP TABLE [#src]

此示例不检查是否有任何值实际更改。如果我们暂时忽略NULL-checking 和 sane fallbacks,这可以通过以下方式之一进行检查:

-- Example #1: updates all rows where first-name or last-name has changed
UPDATE ..
SET ..
FROM ..
WHERE [T].[first_name] <> [S].[first_name] OR [T].[last_name] <> [S].[last_name]

-- Example #2: updates all rows, sets target-value to source-value if value has changed
UPDATE ..
SET 
    ISNULL(NULLIF([S].[first_name], [T].[first_name]), [T].[first_name]),
    ISNULL(NULLIF([S].[last_name], [T].[last_name]), [T].[last_name])
FROM ..

在示例 #1 中,SET 操作将更新所有列,即使只有 1 列已更改。

在示例 #2 中,SET 操作将更新所有行的所有列,如果值未更改则回退到旧值。

在这两个示例中,所有列都被 SET 操作击中,并且根据我的前辈的说法,这会在频繁执行时创建不必要/有问题的事务日志记录量。

这同样适用于MERGE- 语句。即使您检查匹配行的更改,所有列都会被更新命中。

MERGE [#tgt] AS tgt
USING [#src] AS src
ON (tgt.id = src.id)  
WHEN MATCHED AND ([tgt].[first_name] <> [src].[first_name] OR [tgt].[last_name] <> [src].[last_name])
THEN UPDATE SET 
        [tgt].[first_name] = [src].[first_name], 
        [tgt].[last_name] = [src].[last_name];

那么我们该怎么办?对我们希望更新的每一列使用单个 UPDATE 语句。在这种情况下:

-- first_name
UPDATE [T]
SET [T].[first_name] = [S].[first_name]
FROM
    [#tgt] AS [T]
    JOIN [#src] AS [S] ON [S].[id] = [T].[id]
    WHERE [T].[first_name] <> [S].[first_name]

-- last_name
UPDATE [T]
SET [T].[last_name] = [S].[last_name]
FROM
    [#tgt] AS [T]
    JOIN [#src] AS [S] ON [S].[id] = [T].[id]
    WHERE [T].[last_name] <> [S].[last_name]

现在,这种方法有几个缺点:

  • 所有更新语句必须在同一个事务中执行,以确保一行不会被半更新。
  • 编写所有代码真的很糟糕(想象一下有 50 多列的表)

感觉必须有更聪明的方法来解决这个问题,我将不胜感激对这篇文章中所做陈述的任何澄清和更正。如前所述,我只是尽力理解为什么必须这样。

为冗长的帖子道歉,并提前致谢。

sql-server transaction-log
  • 2 2 个回答
  • 379 Views

2 个回答

  • Voted
  1. Best Answer
    Solomon Rutzky
    2017-03-11T09:08:19+08:002017-03-11T09:08:19+08:00

    我的前辈得出结论,我们不能使用 MERGE 或 UPDATE 语句,因为它会产生过多的日志记录,因为它会在同一语句中处理所有列。

    好吧,他们能得出这样的结论真是太好了。但是,他们是否提供了任何证据或他们的测试脚本来显示这种行为?我有兴趣看到这样的测试 ;-)

    这样做的理由是,当您在 SQL Server 中执行更新语句时,当您设置一个列值并且新值等于旧值时,它仍然在事务日志中标记为更新。

    这是其中的一个案例,一点点知识就会产生误导。是的,将列更新为完全相同的值被视为更新,就像测试通过UPDATE()函数更新的列1一样,只要该列在 SET 语句中,就会返回,无论值是否更改。

    但是,缺少的部分是:

    1. 如果没有任何列的值发生变化,则该行实际上并未更新。如果根本没有更新任何行,那么唯一的事务日志活动就是两条记录:一条LOP_BEGIN_XACT标记事务的开始,一条LOP_COMMIT_XACT标记事务的结束。但是没有实际的数据页或索引页被修改。这假设“受影响的行”> 0,但实际上没有任何改变。

    2. 如果所有行都被过滤掉,以至于没有行被更新(即“受影响的行”= 0),则没有Tran Log 活动。

    3. 如果任何列的值发生变化,则将其他列设置为其现有值在事务日志中看起来与未指定值未发生变化的列相同。

    4. 每个查询(除非在显式事务中与其他查询分组)都是它自己的事务,事务日志中的每个事务至少有 2 个条目:一个用于 BEGIN,一个用于 COMMIT 或 ABORT。

    因此:

    • 就 Tran Log 而言,您的“示例 1”和“示例 2”这两个选项几乎相同。如果至少有一行要更新,那么它们应该是相同的。但是,如果没有任何列的值发生变化的行,那么“示例 1”(带有WHERE子句)将导致较少的 Tran Log 活动,因为没有条目,而在“示例 2”中(所有行“已更新”)将有 BEGIN 和 COMMIT 条目。因此,我建议使用该WHERE子句,因为它在您的意图中是明确的,并且会导致稍微减少 Tran Log 活动。

    • 遵循“前辈”的建议肯定会导致更多的Tran Log 活动,更不用说性能下降了。为什么?因为:

      • 在某些情况下,如果名字和姓氏都已更改,则同一行将被标记为需要更新。即使您将这两个UPDATE语句包装到一个显式事务中以减少不一致以及额外的 BEGIN / END 日志条目,在某些情况下您仍会多次更新该行,并且每次修改都会被记录下来。
      • 即使数据行已经在内存中,每个UPDATE语句重新扫描它们仍然需要更多时间。
    • 确切地知道并亲眼所见总是比依靠猜测或别人声称的要好。为此,您应该测试您的各种选项,包括您的前辈建议的两个单独的更新,并在每次测试后通过以下方式检查:

      SELECT   *
      FROM     sys.fn_dblog(NULL, NULL) tl
      ORDER BY tl.[Transaction ID] DESC; -- most recent first
      

    PS 我在 SQL Server 2012 (SP3) Developer Edition 上进行了初步测试。然后,我在 SQL Server 2016 (RTM) Express Edition 上再次测试,结果是一样的。

    PPS 逻辑上和[T].[first_name] = ISNULL(NULLIF([S].[first_name], [T].[first_name]), [T].[first_name])没什么区别[T].[first_name] = [S].[first_name],只是包裹了更多的功能。但是,如果两列都是'A',那么使用'A'来自同一个表的an 更新它与'A'来自另一个表的 an 更新是完全相同的操作。

    PPPS 在检查字符串字段中的任何差异时,您确实需要使用二进制排序规则,否则可能会更改仅大小写(或宽度或组合字符等),以便列的排序规则将比较值相同. 我确实知道你提到的那些是简化的例子,但我只是确保这方面没有被忽视 :-)。因此:

    WHERE [T].[first_name] <> [S].[first_name] OR [T].[last_name] <> [S].[last_name]
    

    变成:

    WHERE [T].[first_name] <> [S].[first_name] COLLATE Latin1_General_100_BIN2
    OR    [T].[last_name] <> [S].[last_name] COLLATE Latin1_General_100_BIN2
    

    和:

    ISNULL(NULLIF([S].[first_name], [T].[first_name]), [T].[first_name]),
    ISNULL(NULLIF([S].[last_name], [T].[last_name]), [T].[last_name])
    

    变成:

    ISNULL(NULLIF([S].[first_name] COLLATE Latin1_General_100_BIN2, [T].[first_name]), [T].[first_name]),
    ISNULL(NULLIF([S].[last_name] COLLATE Latin1_General_100_BIN2, [T].[last_name]), [T].[last_name])
    
    • 3
  2. paparazzo
    2017-03-11T12:32:15+08:002017-03-11T12:32:15+08:00

    您的日志记录级别是多少?你知道简单会清除语句之间的事务日志吗?

    如果您想最小化日志记录,则进行多次更新

    UPDATE [T]
       SET [T].[first_name] = [S].[first_name]
      FROM [#tgt] AS [T]
      JOIN [#src] AS [S] 
        ON [T].[id] = [S].[id]
       AND [T].[first_name] != [S].[first_name];
    
    UPDATE [T]
       SET [T].[first_name] = [S].[first_name]
      FROM [#tgt] AS [T]
      JOIN [#src] AS [S] 
        ON [T].[id] = [S].[id]
       AND [T].[first_name] is null and [S].[first_name] is not null;
    
    UPDATE [T]
       SET [T].[first_name] = null
      FROM [#tgt] AS [T]
      JOIN [#src] AS [S] 
        ON [T].[id] = [S].[id]
       AND [T].[first_name] is not null and [S].[first_name] is null;
    

    然后对 last_name 做同样的事情

    我知道它看起来很长,但它会最大限度地减少日志记录
    多次更新通常比 OR 更有效

    呃,哦,我刚刚读了同样的交易

    一个或另一个会有很大的不同

    视图呢

    只需在更新期间使用视图 - 这个视图应该非常有效

    select case when [S].[ID] == null 
                     then [T].[first_name]
                     else [S].[first_name]
                end as [first_name]
         , case when [S].[ID] == null 
                     then [T].[last_name]
                     else [S].[last_name]
                end as [last_name]
    from      [#tgt] [T] 
    left join [#src] [S]
      on [t].[ID] = [S].[ID]
    
    • 0

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve