如何减少SQL Server事务日志使用量

时间:2016-04-16 07:45:51

标签: sql-server tsql azure

我们有一个在Azure SQL表中写入日志的应用程序。该表的结构如下。

CREATE TABLE [dbo].[xyz_event_history]
(
    [event_history_id] [uniqueidentifier] NOT NULL,
    [event_date_time] [datetime] NOT NULL,
    [instance_id] [uniqueidentifier] NOT NULL,
    [scheduled_task_id] [int] NOT NULL,
    [scheduled_start_time] [datetime] NULL,
    [actual_start_time] [datetime] NULL,
    [actual_end_time] [datetime] NULL,
    [status] [int] NOT NULL,
    [log] [nvarchar](max) NULL,

    CONSTRAINT [PK__crg_scheduler_event_history] PRIMARY KEY NONCLUSTERED 
    (
        [event_history_id] ASC
    )
)

scheduled_task_id列(非唯一)存储为聚簇索引的表。

CREATE CLUSTERED INDEX [IDX__xyz_event_history__scheduled_task_id] ON [dbo].[xyz_event_history]
(
    [scheduled_task_id] ASC
)

应用程序生成的event_history_id,它是随机(非顺序)GUID。应用程序可以从表中创建,更新和删除旧实体。 log列通常包含2-10 KB的数据,但在某些情况下可能会增长到5-10 MB。这些项目通常由PK(event_history_id)访问,最常见的排序顺序为event_date_time desc

我们将Azure SQL的性能层降低到“S3”(100 DTU)后看到的问题是跨越事务日志速率限制。可以在sys.dm_exec_requests表中清楚地看到 - 将有等待类型LOG_RATE_GOVERNORmsdn)的记录。

  

当DB等待配额写入日志时发生。

我注意到,对日志率造成重大影响的操作是xyz_event_history的删除和log列中的更新。更新以下列方式进行。

UPDATE xyz_event_history
SET [log] = COALESCE([log], '') + @log_to_append
WHERE event_history_id = @id

Azure SQL数据库的恢复模式为FULL,无法更改。

这是物理索引统计信息 - 有许多页面每行限制超过8K。

TableName           AllocUnitTp PgCt    AvgPgSpcUsed        RcdCt   MinRcdSz    MaxRcdSz
xyz_event_history   IN_ROW_DATA 4145    47.6372868791698    43771   102         7864
xyz_event_history   IN_ROW_DATA 59      18.1995058067705    4145    11          19
xyz_event_history   IN_ROW_DATA 4       3.75277983691623    59      11          19
xyz_event_history   IN_ROW_DATA 1       0.914257474672597   4       11          19
xyz_event_history   LOB_DATA    168191  97.592290585619     169479  38          8068
xyz_event_history   IN_ROW_DATA 7062    3.65090190264393    43771   38          46
xyz_event_history   IN_ROW_DATA 99      22.0080800593032    7062    23          23
xyz_event_history   IN_ROW_DATA 1       30.5534964170991    99      23          23
xyz_event_history   IN_ROW_DATA 2339    9.15620212503089    43771   16          38
xyz_event_history   IN_ROW_DATA 96      8.70488015814184    2339    27          27
xyz_event_history   IN_ROW_DATA 1       34.3711391153941    96      27          27
xyz_event_history   IN_ROW_DATA 1054    26.5034840622683    43771   28          50
xyz_event_history   IN_ROW_DATA 139     3.81632073140598    1054    39          39
xyz_event_history   IN_ROW_DATA 1       70.3854707190511    139     39          39
  • 有没有办法减少事务日志的使用?
  • 如上例所示,SQL Server如何记录更新事务?它只是“旧”加上“新”价值吗? (可以想象,在事务日志大小方面,通常会添加很少的数据)

更新(4月20日): 我已经在答案中做了一些实验,并对INSERT代替UPDATE的差异印象深刻。

根据以下关于SQL Server事务日志内部的msdn文章(https://technet.microsoft.com/en-us/library/jj835093(v=sql.110).aspx):

  

数据修改的日志记录记录逻辑操作   执行或他们记录修改后的图像   数据。前映像是操作之前的数据副本   执行;后映像是操作后数据的副本   已经完成了。

这会在事务日志使用方面自动使UPDATE ... SET X = X + 'more' 非常低效的方案 - 它需要“捕获图像前”。

我创建了简单的测试套件来测试向“log”列添加数据的原始方式,而不是我们只是将新数据插入新表的方式。我得到的结果相当惊人(至少对我来说,对SQL Server人员没有太多经验)。

测试很简单:5'000次添加1'024个字符长的日志部分 - 结果只有5MB的文本(不像人们想象的那么糟糕)。

FULL recovery mode, SQL Server 2014, Windows 10, SSD
                    UPDATE         INSERT
Duration             07:48 (!)      00:02
Data file grow        ~8MB           ~8MB
Tran. Log grow      ~218MB (!)        0MB (why?!)

enter image description here

添加1KB数据的5000个更新可以在8分钟内挂出SQL Server(哇!) - 我没想到!

我认为原始问题已在此时得到解决,但提出了以下问题:

  1. 为什么事务日志增长看起来是线性的(不是像我们在简单捕获“之前”和“之后”图像时所期望的那样二次方?)从图中我们可以看到“每秒项目数”按比例增长到平方根 - 如果开销与插入的项目数量呈线性增长,则符合预期。
  2. 为什么插入事务日志似乎与任何插入之前的大小相同? 我已经看了一下事务日志(带Dell's Toad)的情况下插入的情况,看起来只有最后297个项目在那里 - 可以想象事务日志被截断,但为什么如果FULL恢复模式?
  3. 更新(4月21日)。 DBCC LOGINFO案例的INSERT输出 - 之前和之后。日志文件的物理大小与输出匹配 - 磁盘上恰好为1,048,576字节。 为什么事务日志仍然存在?

    RecoveryUnitId  FileId  FileSize    StartOffset FSeqNo  Status  Parity  CreateLSN
    0               2       253952      8192        131161  0       64      0        
    0               2       253952      262144      131162  2       64      0        
    0               2       253952      516096      131159  0       128     0        
    0               2       278528      770048      131160  0       128     0        
    
    RecoveryUnitId  FileId  FileSize    StartOffset FSeqNo  Status  Parity  CreateLSN
    0               2       253952      8192        131221  0       128     0        
    0               2       253952      262144      131222  0       128     0        
    0               2       253952      516096      131223  2       128     0        
    0               2       278528      770048      131224  2       128     0        
    

    对于那些感兴趣的人我使用Process Monitor记录了“sqlserv.exe”活动 - 我可以看到该文件被一次又一次地覆盖 - 看起来SQL Server将旧日志项视为由于某种原因不再需要:https://dl.dropboxusercontent.com/u/1323651/stackoverflow-sql-server-transaction-log.pml

    更新(4月24日)。似乎我终于开始了解那里发生了什么,并希望与您分享。上面的推理总的来说是正确的,但是有一个严重的警告,这也引起了对INSERT s的奇怪事务日志重用的混淆。

      

    数据库的行为类似于SIMPLE恢复模式,直到第一次完整   进行备份(即使它处于完全恢复模式)。

    我们可以将上面的数字和图表视为SIMPLE恢复模式的有效数字,我必须重做 real FULL的衡量标准 - 它们更加惊人

                        UPDATE         INSERT
    Duration             13:20 (!)      00:02
    Data file grow         8MB           11MB
    Tran. log grow      55.2GB (!)       14MB
    

    real FULL recovery mode UPDATE stats

2 个答案:

答案 0 :(得分:3)

您使用日志字段违反了普通表单的基本租户之一。日志字段接口将保留与主要信息相关的附加信息序列。解决方法是停止这样做。

1创建一个表。 xyz_event_history_LOG(event_history_id,log_sequence#,日志)

2停止对[xyz_event_history]中的日志字段进行更新,而是插入xyz_event_history_LOG

事务日志中的数据量将大幅减少。

答案 1 :(得分:1)

事务日志按照创建顺序包含对数据库的所有更改,因此如果多次更新行,您将获得该行的多个条目。它确实存储了整个值,无论是新的还是新的,所以对于大数据类型(如nvarchar(max))的多次小更新效率低是正确的,如果它们只是很小的话,最好将更新存储在单独的列中值。