SQL Server中的事务日志管理(7/9):处理日志过度增长 您所在的位置:网站首页 增大数据库的容量的方法有 SQL Server中的事务日志管理(7/9):处理日志过度增长

SQL Server中的事务日志管理(7/9):处理日志过度增长

2024-07-10 13:33| 来源: 网络整理| 查看: 265

当一切正常时,没有必要特别留意什么是事务日志,它是如何工作的。你只要确保每个数据库都有正确的备份。当出现问题时,事务日志的理解对于采取修正操作是重要的,尤其在需要紧急恢复数据库到指定点时。这系列文章会告诉你每个DBA应该知道的具体细节。

这篇文章会列出导致事务日志过度增长的常见的问题和错误管理形式,包括:

在完整恢复模式里,没有进行日志备份 进行索引维护 长时间运行或未提交的事务阻止事务日志里空间重用

当然,如果增长没检查,日志文件会扩展直到吞没所有可用磁盘空间或日志文件的最大大小,在这个时候你会收到该死的9002错误(事务日志已满),数据库会变成只读。这篇文章会谈到处理日志不断增长和9002错误的正确方法,还会解释下通常建议截断或收缩事务日志为什么是危险的。

最后,我们会谈下保证你日志文件是平稳和可预见增长的措施,还有日志碎片的最小化问题。在忙碌的数据库里,大型的事务日志会是一个简单的生活事实,如果管理妥当的话,这并不是件坏事,即使大部分时间日志文件空间不可用。

日志大小和增长

任何时候日志文件需要增长,额外的空间被分配,这个空间平均分到VLS里,基于被分配空间数。

例如,日志文件默认会有2MB的初始大小,10%的自动增长率(来自model数据库的配置)。这就是说,日志文件开始至少会很小的增长,因此会有大量的小VLF。

当我们在很大的块上分配额外空间时,例如当一次操作初始16GB的大小,结果事务日志会有很小数量的VLF。

太高数量的小VLF,这样的情况称为日志文件碎片,会影响到性能,尤其在故障恢复,还原和备份,特别是日志备份。换句话说,它会影响读取日志文件的操作性能。我们会在第8篇详细讨论这个问题。

事务日志VLF——太多还是太少?

SQL Server MVP的Kimberly Tripp在他的文章里讨论了VLF大小的影响,并提供了如何恰当管理VLF大小的指导——事务日志VLF—太多还是太少?

相反,如果日志文件只有几个很大的VLF,我们有长时间占用大块日志的风险。每个VLF都有很大数量的日志记录,SQL Server不能截断VLF直到它没有包含活动日志。这个情况下截断会因某些原因延迟(在缺少日志空间重用部分会详细谈到),这会导致日志的快速增长。例如,我们假设每个VLF是1GB大小且日志满了。你进行了一次日志备份,但是所有的VLF包含活动日志的一部分,SQL Server不能截断日志。它没有别的选择只能增加更多VLF,如果日志的增长率设置为同等大小,那么日志增长会很快,直到有VLF变成可截断。

因此,正确设置日志初始大小非常重要,那它的增长才会是合适的大小步骤,最小化日志碎片也避免了过快增长。

正确设置初始大小且可控制它的增长的第2个原因是:对于日志文件,每个增长是相对昂贵的操作。数据和日志文件增长超时是正常的。SQL Server可以优化增加新数据文件和扩展现有数据文件的过程,通过即时文件初始化(instant file initialization)(在SQL Server 2005引入,允许在磁盘上分配空间给数据文件,而不需要进行填零)。遗憾的是,对于日志文件是不一样的,对于日志文件创建或增长的空间分配,还是需要初始化且填零。

为什么事务日志不能使用即时初始化?

进一步关注事务日志填零,看下Paul Randal的文章:http://sqlskills.com/BLOGS/PAUL/post/Search-Engine-QA-24-Why-cant-the-transaction-log-use-instant-initialization.aspx

事务日志不断增长的诊断

如果你经历事务日志的不可控增长,它由于要么是活动日志频率太高,要么是有因素阻止日志文件里的空间重用,或者两者都有。

如果增长的主要原因是活动日志过多,你要检查下是否可以避免这个活动,例如调整处理大容量数据和索引维护的数据库模式,这样的话这些操作不会完整记录(例如针对这些操作使用大容量日志恢复模式)。但是,如果日志备份里包含有任何的最小化日志操作,大容量操作会立即阻止数据库到时间点的恢复(可以阅读下第6篇文章来获得更多详细信息)。如果这是不可接受的,你必须直接接受大日志的事实,根据具体情况计划它的增长和管理(例如日志备份频率),在接下来的妥当的日志管理会介绍。

如果增长原因是缺少日志空间的重用,你要找出什么阻止这个重用并采取措施来修正这个问题。

日志过度增长:索引维护操作

索引维护操作是个很常见的导致事务日志使用率过度和增长的原因,尤其数据库运行在完整恢复模式里。进行索引维护需要日志空间量取决于下列因素:

重建还是重组——通常索引重建在日志里会使用更多的空间 恢复模式——如果时间点恢复的风险已理解且可接受的,那么索引重建可以临时通过切换到大容量恢复模式里的最小化日志。但索引重组始终是完整记录的。 索引重建

当重建索引时,不管在线还是离线,使用ALTER INDEX REBUILD,或是已经废弃的SQL Server 2000里的DBCC DBREINDEX,SQL Server新创建一个索引的副本,然后一旦重建完整,删除老的副本(这是为什么你至少需要数据文件里索引大小一样的可用空间)

日志记录和在线索引重建

在SQL Server 2008和后续版本,在线索引重建是完整日志操作,在SQL Server 2005里是最小化日志。因此,在后续SQL Server版本上进行这样的操作本质上需要更多的事务日志空间,可以看下:https://support.microsoft.com/zh-cn/kb/2407439 还有Kalen Delaney的日志,对于完整和大容量日志2个恢复模式,验证下在在线和离线索引重建期间的日志记录。

在完整恢复模式里,索引重建可以是非常占资源的操作,需要事务日志里的很多空间。在简单或大容量日志恢复模式里,重建索引是最小化日志操作,这意味着只有分配被记录,实际的页并没改变,因此通过这个操作减少了日志空间量。

如果你切换到简单模式进行索引重建,LSN链会立即中断。你只能恢复你的数据库到刚才事务日志备份里的包含的时间点。为了重新开始日志链,你需要切换回完整恢复模式并立即进行一次完整或差异数据库备份。

如果你切换到大容量日志模式(看下第6篇),LSN链还是连续的,但还会影响到进行时间点的恢复,因为包含最小化日志操作的日志备份不能用来恢复到时间点。如果能恢复到时间点的要求是至高无上的,那么索引重建或任何最小化日志操作不要使用大容量日志恢复模式。除非在数据库里没有同时发生的用户活动,你可以使用。不然的话,在可能的情况下考虑在完整恢复模式里进行索引重建。

如果使用的是大容量日志模式,尽可能使时间点恢复的时间最小,这样可以最小化暴露数据丢失风险。为了做到这一点,在完整模式里进行日志备份,切换到大容量日志,进行索引重建,然后切换回完整进行另一个日志备份。

最后一个重点要记住的是ALTER INDEX REBUILD操作是在一个单独事务里。如果索引很大,事务的执行时间会很长,在期间,这会阻止日志里的空间重用。这就是说,即使你在简单模式里重建索引,你也要想到自检查点(CHECKPOINT)操作后日志应该保持很小,重建是最小化日志,在剧烈的重建期间,日志文件还会快速扩展。

索引重组

和重建索引相比,使用ALTER INDEX REORGANIZE或者SQL Server里的DBCC INDEXDEFRAG(已废弃)重组(碎片整理)索引都是完整记录操作,不管是任何恢复模式,因此实际的页修改总被记录。但是,通常索引重组比索引重建需要更少的日志空间,尽管这是索引里降低碎片的一个功能;比起轻度碎片,重度碎片索引会需要更多的日志空间来重组。

另外,ALTER INDEX REORGANIZE操作是通过多个更短的事务完成的。因此,当与定期的日志备份相结合(或在简单恢复模式里)时,在此操作期间,日志空间可以被重用,因此要求操作期间日志空间最小化。

例如,对于重建操作,重建20GB的索引会需要超过20GB的空间,因为它发生在一个单独的事务里。但是,重组20GB的索引会需要更少的空间,因为在重组里每个页分配修改是个单独的事务,因此日志记录可以用定期日志备份截断,让日式空间可以重用。

控制日志过度措施

如果你的组织对任何潜在数据丢失不能容忍的,那么你没有选择,只能让所有的数据库运行在完整恢复模式里,并且妥当计划你的日志大小和增长。因此索引重建是作为一个单独线程发生的,日志至少会和你重建的索引一样的大小。如刚才所说,索引重组会需要更少的空间,且允许在操作期间通过日志备份来截断日志。这样的话,为了同时避免日志暴涨,可行的话,你可以用日志重组。

如果你的SLA和操作级别协议(Operational Level Agreements(OLAs))允许一些潜在的数据丢失,那么在索引重建前l切换到大容量日志恢复模式可以为重建索引最小化空间需求量。但是,要在最小化数据丢失的方式下进行,例如已经讨论确认过了。

不管使用的恢复模式,你可以通过重组索引而不是重建索引来在日志上最小化索引维护操作的影响。可以的话,可以看下微软的指导方针,为了最小化索引维护操作的影响,对于绝大数情况,并不是所有情况,决定什么时候进行索引重建,什么时候进行索引重组(查看索引重组和重建)。他们也声明:对于碎片级别大于5%且小于30%,你应该重组索引,对于碎片级别大于30%,你应该重建它。

但是,在索引维护期间,在保护日志过度增长里,最有效的武器是维护那些真正需要的索引。使用SSMS维护计划向导,索引维护是个孤注一掷的操作:要么重建(或重组)数据库里(维护计划里的所有数据库)的所有索引,要么全不维护。一个更好的方法是使用sys.dm_db_index_physical_stats的DMV来看下碎片程度根据需要来决定索引重建/重组策略。

Ola Hallengren的免费维护脚本

Ola Hallengren提供一个综合的免费维护工具,它展示了如何使用sys.dm_db_index_physical_stats进行索引分析来进行智能维护,它可以用来代替SSMS里向导创建的数据库维护计划(https://ola.hallengren.com/)。

但是最好的方法,是计划只维护那些可以在查询上提供真正持久影响的索引。逻辑碎片(在乱序中的索引页)挫败了SQL Server的预读机制(https://msdn.microsoft.com/zh-cn/library/ms191475%28v=sql.105%29.aspx),且使在磁盘上读取连续页I/O-效率更低。但是,这只真正影响从磁盘的大范围扫描。即使对非常大碎片的索引,如果你不扫描表,重建或重组索引不会提高性能。降低页深度(通过页分裂或删除造成的很多缺口)会带来更多的页占用磁盘空间,且在内存里,会需要更多的I/O带宽来传输数据。再说一次,这个碎片格式不会真正影响不频繁修改的索引,因此重建它们不会有帮助。

计划索引维护前,问下自己什么性能标准从维护受益?它会大会减少I/O?它会提高你最昂贵查询的多少性能?它是持久正面影响么?如果这些答案是“否”或“不知道”,那么定期索引维护可能不是个长远的答案。最后,值得注意的是对小索引维护是不值得的。通常引用的阈值是近1000页。在微软,当Paul Randal管理存储引擎开发团队时,建议这些值作为参考,在在线帮助里记录了。注意,尽管这只是个建议并不对所有环境合适,如Paul在他的博客文章里谈到的:“在线帮助的索引碎片阈值来自哪里?”

调查重日志写入事务

sys.dm_tran_database_transactions的DMV提供在事务日志上事务活动影响的有用内部信息。在他们的书里,《使用SQL Server动态管理视图进行性能调优》,得到他们的允许后,复制在这里,作者Louis Davidson和Tim Ford,演示了如何使用这个DMV和一些其他的,来调查可能造成事务日志过度增长的事务。

在代码7.1里的例子重用来自第6篇的FullRecovery数据库和PrimaryTable_Large表。在一个显性事务里,它重建了聚集索引然后调查日志增长。

1 USE FullRecovery 2 GO 3 BEGIN TRANSACTION 4 5 ALTER INDEX ALL ON dbo.PrimaryTable_Large REBUILD 6 7 SELECT DTST.[session_id], 8 DES.[login_name] AS [Login Name], 9 DB_NAME (DTDT.database_id) AS [Database], 10 DTDT.[database_transaction_begin_time] AS [Begin Time], 11 DATEDIFF(ms, DTDT.[database_transaction_begin_time], GETDATE()) 12 AS [Duration ms] , 13 CASE DTAT.transaction_type 14 WHEN 1 THEN 'Read/write' 15 WHEN 2 THEN 'Read-only' 16 WHEN 3 THEN 'System' 17 WHEN 4 THEN 'Distributed' 18 END AS [Transaction Type], 19 CASE DTAT.transaction_state 20 WHEN 0 THEN 'Not fully initialized' 21 WHEN 1 THEN 'Initialized, not started' 22 WHEN 2 THEN 'Active' 23 WHEN 3 THEN 'Ended' 24 WHEN 4 THEN 'Commit initiated' 25 WHEN 5 THEN 'Prepared, awaiting resolution' 26 WHEN 6 THEN 'Committed' 27 WHEN 7 THEN 'Rolling back' 28 WHEN 8 THEN 'Rolled back' 29 END AS [Transaction State], 30 DTDT.[database_transaction_log_record_count] AS [Log Records], 31 DTDT.[database_transaction_log_bytes_used] AS [Log Bytes Used], 32 DTDT.[database_transaction_log_bytes_reserved] AS [Log Bytes RSVPd], 33 DEST.[text] AS [Last Transaction Text], 34 DEQP.[query_plan] AS [Last Query Plan] 35 FROM sys.dm_tran_database_transactions DTDT 36 INNER JOIN sys.dm_tran_session_transactions DTST 37 ON DTST.[transaction_id] = DTDT.[transaction_id] 38 INNER JOIN sys.[dm_tran_active_transactions] DTAT 39 ON DTST.[transaction_id] = DTAT.[transaction_id] 40 INNER JOIN sys.[dm_exec_sessions] DES 41 ON DES.[session_id] = DTST.[session_id] 42 INNER JOIN sys.dm_exec_connections DEC 43 ON DEC.[session_id] = DTST.[session_id] 44 LEFT JOIN sys.dm_exec_requests DER 45 ON DER.[session_id] = DTST.[session_id] 46 CROSS APPLY sys.dm_exec_sql_text (DEC.[most_recent_sql_handle]) AS DEST 47 OUTER APPLY sys.dm_exec_query_plan (DER.[plan_handle]) AS DEQP 48 WHERE DB_NAME(DTDT.database_id) = 'FullRecovery' 49 ORDER BY DTDT.[database_transaction_log_bytes_used] DESC; 50 -- ORDER BY [Duration ms] DESC; 51 COMMIT TRANSACTION

(代码7.1:调查重日志写入事务)

(插图7.1:索引重建后日志活动结果)

顺便提下,如果我们用ALTER INDEX...REORGANIZE来运行这个例子,那么在Log Bytes Used列的值会从近159M降为近0.5M。

缺少日志空间重用

如果你怀疑缺少日志空间重用造成了日志增长,你的第一个任务是找出什么阻止了重用。开始通过查询如代码7.2所示的sys.databases,看下对于提到的数据库log_reuse_wait_desc的列值错误信息是什么。

1 SELECT name , 2 recovery_model_desc , 3 log_reuse_wait_desc 4 FROM sys.databases 5 WHERE name = 'FullRecovery'

(代码7.2:检查下log_reuse_wait_desc的列值)

log_reuse_wait_desc的列值会展示为什么当前空间不被重用的原因。如果你已经执行刚才的例子(代码7.1),那么很可能FullRecovery数据库在这列会显示LOG_BACKUP值(下面会详谈)。

阻止日志重用不止一个。sys.databases视图只显示其中一个原因。因此它是解决问题的一个可能方法,再次查询sys.database会看到log_reuse_wait不同的原因。

在在线帮助里列出了log_reuse_wait_desc所有可能值,但在这里我们只谈最常见的原因,解释如何安全确保那个空间可以被重用。

没有日志备份的完整恢复模式

如果从sys.databases查询,log_reuse_wait_desc的返回值是LOG_BACKUP,那么你很可能遭受完整或大事务日志的最常见原因,即在完整恢复模式里的数据库(或次之,大容量日志恢复模式),没有进行事务日志备份。

在SQL Server的很多版本里,model数据库默认是完整恢复模式。因为model数据库是创建所有新SQL Server用户数据库的模板,新的数据库继承自model的配置。

对于大多数生产数据库,使用完整恢复模式是推荐的做法,因为它允许数据库的时间点恢复,最小化灾难事件的数据丢失。但是,接下来的常见错误是调整备份策略是只有完整备份(或者有差异备份)而没有定期的事务日志备份。这个策略有2个大问题:

进行完整数据库备份只保护数据文件内容,没有日志文件内容。完整保护已改变数据的唯一方法是自完整或差异备份后,需要时间点的恢复,是进行日志备份。 完整数据库备份不会截断事务日志。只有日志备份会造成日志文件截断。没有的话,日志文件里的空间是从不标记重用的,日志文件会不停的增长。

为了进行时间点的恢复并控制日志大小,我们必须用数据库完整或完整和差异备份连同事务日志备份。对于我们的FullRecovery数据库,我们可以进行日志备份,如代码7.3所示,然后再次查询sys.databases。

1 USE master 2 GO 3 BACKUP LOG FullRecovery 4 TO DISK = 'D:\SQLBackups\FullRecovery_log.trn' 5 WITH INIT 6 GO 7 8 SELECT name , 9 recovery_model_desc , 10 log_reuse_wait_desc 11 FROM sys.databases 12 WHERE name = 'FullRecovery'

(代码7.3:解决日志备份问题)

如果缺少日志备份是日志增长问题的原因,首先要做的是验证问题数据库是否真的需要运行在完整恢复模式。如果必须要能恢复数据库到任意时间点或到灾难事件前的一个时间点,则是必须的,或者必须要用完整恢复模式的另一个原因(例如数据库镜像)。如果在SLA里目标恢复点( Recovery Point Objective (RPO) )为最大15分钟的数据丢失,那么很可能你不能只进行完整数据库备份和差异数据库备份,必须要进行日志备份。

但是,如果因为不需要而没有进行日志备份,那么数据库不应该运行在完整恢复模式;我们可以切换数据库到简单恢复模式,那事务日志的不活动部分会自动标记为可重用,在检查点的时候。

如果数据库需要运行在完整恢复模式,那么开始日志备份,或调查下备份需要的频率。事务日志的备份频率取决于很多因素,例如数据修改的频率,还有在灾 难中,SLA上可接受的数据丢失程度。另外,你应该采取措施保证日志增长是可控的,在将来是可预见的,在这篇文章里的妥当的日志管理部分会介绍。

活动事务

如果log_reuse_wait_desc的返回值是ACTIVE_TRANSACTION,那么你受到来自SQL Server里完整或大的事务日志的第二个常见原因:长时间运行或未提交的事务。重新执行下来自代码7.1的事务,但不提交,在重新执行下代码7.3,你 会看到这个值返回(不要忘记回去提交这个事务)。

如在第2篇日志截断和空间重用部分介绍的,事务日志里的VLF只有在不包含活动日志部分时才会被截断。如果数据库试用完整或大容量日志恢复模式,只 有日志备份操作才可以进行截断。数据库里长时间运行的事务延迟包含事务开始后生成的日志记录的VLF的截断,包括其它并发事务对数据库里的数据修改产生的 日志记录,甚至当这些改变还没提交时。另外,长时间运行的事务的空间需求量会通过对“补偿日志记录”保留的空间增加,如果在系统里事务回滚的话,这些日志 记录就会产生。这些保留是需要的,保证在回滚期间,这些事务可以成功恢复而不会用完日志空间。

另一个常见对log_reuse_wait_desc值的活动事务值是“孤立”的显式事务,它莫名其妙的从不提交。允许用户在事务里输入的应用程序就特别容易是这类问题。

长时间运行的事务

造成长时间运行的事务的最常见操作,也是在数据库里生成大量日志记录,是从数据库里归档或清除数据。数据保持往往是数据库设计里事后的想法,经常是数据库已经活跃一段时间后才考虑,是在服务器接近可用存储的容量限制。

通常,当需要归档时,第一个反应是从数据库里使用简单的DELETE语句删除不需要的数据,如代码7.4所示。为了生成一些简单的测试数据,这个脚本使用Jeff Moden的随机数据生成器的简化版本,简单修改来生成日期到2012。

USE FullRecovery ; GO IF OBJECT_ID('dbo.LogTest', 'U') IS NOT NULL DROP TABLE dbo.LogTest ; SELECT TOP 500000 SomeDate = CAST(RAND(CHECKSUM(NEWID())) * 3653.0 + 37534.0 AS DATETIME) INTO dbo.LogTest FROM sys.all_columns ac1 CROSS JOIN sys.all_columns ac2 ; -- delete all but the last 60 days of data DELETE dbo.LogTest WHERE SomeDate < GETDATE() - 60

(代码7.4:大容量数据删除)

取决于要删除的在日期范围内存在的行数,这会变成引起日志增长问题的长时间运行的事务,即使数据库运行在简单恢复模式。外键串联约束的出现或审计触 发器会恶化问题。如果其它表引用目标表,通过外键约束来级联删除,那么SQL Server页通过级联约束来记录删除的行的细节。如果表上有DELETE触发器,在触发器执行期间,SQL Server也会记录进行的操作。

为了最小化在事务日志上的影响,数据清理应该简化为更短,独立的事务。有很多方法中断长时间运行的事务为小的批处理。如果表存在级别约束或DELETE触发器,我们可以在循环内进行删除操作,在一个时间删除一天的数据,如果代码7.5所示。注意,在这个简单的例子里,在我们的表里没有足够的行来验证这个技术的使用,简单的DELETE;清理几百万行数据更合适。也注意批量删除的主要关心的是并不是速度(代码7.5会比代码7.4运行更慢)。最要关心的是避免日志过度增长和锁升级。

DECLARE @StopDate DATETIME , @PurgeDate DATETIME SELECT @PurgeDate = DATEADD(DAY, DATEDIFF(DAY, 0, MIN(SomeDate)), 0) , @StopDate = DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()) - 60, 0) FROM dbo.LogTest WHILE @PurgeDate


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有