维护期间事务日志增长失控

Don*_*Don 4 sql-server maintenance transaction-log sql-server-2014

我被要求清理表格的几列中的前面和后面的空格。该表是从平面文件导入的,并且有许多输入错误的行。

该表有超过 1.5 亿行。

到目前为止,我已经尝试执行一个更新语句来更新列ltrim(rtrim(columnname)),我还尝试创建一个临时表(堆)并insert into ... select from使用相同的ltrim(rtrim(columnname))语法。

在每种情况下,事务日志都会失去控制,直至耗尽磁盘空间。

我了解事务日志如何工作得相当好。然而,在执行这样的批量维护的情况下,我被难住了。

目前数据库处于大容量日志恢复模式,我每 30 秒运行一次事务日志备份,事务日志的增长速度继续超过我​​可以备份的速度。我已经研究过如何将这项工作分成批次,但似乎无法制定可以有效执行此操作的查询。我有一个可以关闭的身份字段,所有其他列都是varchar列。现在我认为答案是简单地为服务器获得更多磁盘空间,但是我希望有更好的解决方案。

我使用的是 Microsoft SQL Server 2014 企业版。

平面文件早已不复存在;这是一个在 10 年和 SQL Server 的许多版本中不断增长的表。只执行较小的增量批量更新,我相信新插入在插入之前已经被清理。所以我正在修理旧东西:(

Han*_*non 6

您应该能够对此表进行批量更新,而不会导致日志不受控制地增长。

这是一个简单的测试平台,用于展示我将如何处理此问题:

USE tempdb;

SET NOCOUNT ON;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL DROP TABLE dbo.Table1;
CREATE TABLE dbo.Table1
(
    id int NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , COL1 nvarchar(150) NOT NULL
    , COL2 nvarchar(150) NOT NULL
) ON [PRIMARY];

INSERT INTO dbo.Table1 (COL1, COl2)
SELECT N'     ' + sc1.name + N'     '
    , N'     ' + sc2.name + N'     '
FROM sys.syscolumns sc1
    CROSS JOIN sys.syscolumns sc2;

GO
Run Code Online (Sandbox Code Playgroud)

这实际上删除了前导和尾随空格,每批 4,000 行:

DECLARE @LogFreePercent DECIMAL(5,2);

/* this temp table will hold all the ID values for
   rows we need to update
*/
IF OBJECT_ID(N'tempdb..#ids', N'U') IS NOT NULL 
DROP TABLE #ids;
CREATE TABLE #ids
(
    id int NOT NULL 
) ON [PRIMARY];
CREATE CLUSTERED INDEX #ids_cx ON #ids(id);

/* this temp table will hold all the ID values for
   rows we've already updated
*/
IF OBJECT_ID(N'tempdb..#fixed_ids', N'U') IS NOT NULL 
DROP TABLE #fixed_ids;
CREATE TABLE #fixed_ids
(
    id int NOT NULL 
) ON [PRIMARY];
CREATE CLUSTERED INDEX #fixed_ids_cx ON #fixed_ids(id);

/* Insert the ID values for rows with leading or trailing
   spaces.  This is not efficient, but should do the trick,
   and ensures we only update rows that actually have leading
   or trailing spaces.
*/
INSERT INTO #ids (id)
SELECT id
FROM dbo.Table1 WITH (NOLOCK)
WHERE COL1 LIKE ' %'
    OR COl1 LIKE '% '
    OR COL2 LIKE ' %'
    OR COl2 LIKE '% ';

/* Run a loop while we still have IDs that need processing
*/
WHILE EXISTS (SELECT TOP(1) 1 FROM #ids)
BEGIN
    /* Use a CTE to select 4000 rows to update from the 
       temp table, outputing the IDs we've fixed into 
       the #fixed_ids table.
    */
    ;WITH src AS (
        SELECT TOP(4000) Table1.*
        FROM dbo.Table1
            INNER JOIN #ids i ON i.id = Table1.id
    )
    UPDATE src
    SET COL1 = LTRIM(RTRIM(COL1))
        , COL2 = LTRIM(RTRIM(COL2))
    OUTPUT inserted.id
    INTO #fixed_ids;

    /* Show some status details
    */
    PRINT (CONVERT(nvarchar(30), GETDATE(), 120) + N' - updated ' 
        + CONVERT(nvarchar(20), @@ROWCOUNT) + ' rows.')
    SELECT @LogFreePercent = 100 - su.used_log_space_in_percent
    FROM sys.dm_db_log_space_usage su;
    PRINT (N'Log free percent: ' + CONVERT(nvarchar(10), @LogFreePercent));

    /* remove the processed rows from the temp tables
    */
    DELETE FROM #ids 
    WHERE EXISTS (SELECT 1 FROM #fixed_ids fi WHERE fi.id = #ids.id);
    DELETE FROM #fixed_ids;

    /* checkpoint the database to allow log records to be truncated
    */
    CHECKPOINT 1;
END
Run Code Online (Sandbox Code Playgroud)

之后运行它以查看是否有任何行有前导或尾随空格:

SELECT [1] = '|' + t1.COL1 + '|'
    , [2] = '|' + t1.COL2 + '|'
FROM dbo.Table1 t1;
Run Code Online (Sandbox Code Playgroud)

您可能注意到我WITH (NOLOCK)在从源表读取的查询中遇到了可怕的dbo.Table. 这是为了帮助防止我们的流程阻止其他正在进行的交易。由于我们仅使用它来创建我们要操作的 ID 列表,因此这是可以接受的READ UNCOMMITTED.

在我的慢速开发数据库中,这在不到三分钟的时间内处理了 564,000 行,并且日志从未低于 97% 空闲。