)
(代码7.5:将数据清除拆散为小的事务)
使用这个方法清除数据,每个删除事务的持续时间是从表里删除一条记录的时间,加上任何触发器或级联约束进行它们操作的时间。如果数据库使用简单恢复模式,下个检查点会截断这些清除产生的日志记录,只要在VLF里没有相关数据清理的活动日志存在。
当在处理过程中级联约束或审计触发器不是要考虑的,我们可以使用不同的方法来清理表上的数据,同时最小化事务。不是进行一点的DELETE操作,它会影响多少的数据,取决于指定日期里存在的行数,在DELETE语句里使用TOP运算符会限制每个循环操作影响的行数。使用@@ROWCOUNT来捕获DELETE操作影响的行数,运算符会在小的批处理语句里清除数据,直到@@ROWCOUNT的值小于DELETE语句里TOP子句里指定的行数,如代码7.6所示。
这个方法只有在没有触发器和级别约束时使用有效,不然的话@@ROWCOUNT的结果不是实际表删除的行数,而是触发器执行或通过强制级联约束影响的行数。
1 DECLARE @Criteria DATETIME ,
2 @RowCount INT
3 SELECT @Criteria = GETDATE() - 60 ,
4 @RowCount = 10000
5 WHILE @RowCount = 10000
6 BEGIN
7 DELETE TOP ( 10000 )
8 FROM dbo.LogTest
9 WHERE SomeDate < @Criteria
10 SELECT @RowCount = @@ROWCOUNT
11 END
(代码7.6:对于数据清理在DELETE语句里使用TOP运算符)
这些方法在SQL Server 2000,2005,2008的任何版本都可以使用,在数据清理期间最小化事务。
但是,如果你的数据库是SQL Server 2005或2008企业版,且经常清理数据,那么清理数据的更好方法是表分区,在列上筛选要删除的数据。这会更小影响事务日志,因为分区包含的数据会从表转出并清理,对SQL Server只是记录区重新分配的操作。
这已经是这个系列文章讨论范围之外了,自动归档方案。但是,一个可能的归档过程涉及分区,表之间的架构复制,允许一个表的一个分区可以转出到另一个。在主要的OLTP表最小化数据的活动部分,但只减少修改的元数据的归档过程。Kimberley Tripp已经写了一份具体的白皮书,叫做SQL Server 2005里的表和索引分区,它谈了划窗技术(sliding window technique)。
未提交事务
默认情况下,SQL Server会在隐性事务里包裹任何数据修改语句来保证,在灾难事件里,SQL Server可以回滚在故障点已经做出的修改,返回数据到一致的状态。如果修改成功,隐性事务会提交到数据库。和自动发生的隐性事务相比,我们创建显性事务,在代码包裹多个修改在一个事务里,来保证所有的修改通过ROLLBACK命令可以撤销,或者通过COMMIT命令提交让它持久。
当恰当使用时,显性事务可以保证多个表之间的数据修改作为一个单位成功完成,或者全部都不修改。当使用不当时,不管怎样,在数据库里孤立的事务还是活跃的,阻止事务日志的截断,这会导致事务日志增长或填满。在SQL Server里有很多孤立事务的原因,这超出了这篇文章详细介绍的范围。但是,一些常见的原因有:
长时间运行的事务造成应用程序超时
在T-SQL或应用程序代码里错误的错误处理
触发器执行期间失败
链接服务器失效导致孤立的分布式事务
和BEGIN TRANSACTION COMMAND没有对应的COMMIT/ROLLBACK语句
一旦一个事务开始,它会保持活动直到创建的连接,事务触发COMMIT或ROLLBACK语句,或者连接从SQL Server中断(当使用绑定的链接,会允许会话共享锁,这是个异常)。
现在的应用程序通常会使用连接池,在池里保持与SQL Server的连接让程序重用,即使当程序代码在连接上调用Close()方法。当对孤立事务进行故障排除是理解最后一点非常重要,因为即使连接在加入或返回到应用程序连接池前被重置,数据库里打开的事务还是继续存在的,如果它们没有正常结束的话。
识别活动事务
事务相关的DMV提供大量的额外信息,不管当前事务的状态和进行的操作。但是,一些DBA还是使用DBCC OPENTRAN作为识别是否为孤立事务(或只是长时间运行的)为造成日志增长的根源的最快方法。
在DBCC OPENTRAN(DatabaseName)格式里会接受数据库名称作为输入参数,数据库名称是用作检查打开事务的数据库名。如果数据库里有活动事务存在,命令会输出类似如下的信息。
1 DBCC OPENTRAN (FullRecovery)
Transaction information for database 'FullRecovery'.
Oldest active transaction:
SPID (server process ID): 56
UID (user ID) : -1
Name : user_transaction
LSN : (897:15322:1)
Start time : Sep 18 2012 1:01:29:390PM
SID : 0x010500000000000515000000fd43461e19525f12828ba628ee0a0000
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
(代码7.7:来自DBCC OPENTRAN的输出信息范例)
DBCC OPENTRAN只输出最早的活动事务,但主要表示事务是否为活动的疑问是开始时间。一般来说,未提交的事务是打开很长时间才会是造成事务日志增长的原因。
另一个重要的信息是SPID(server process ID;在DMV里这用session_id代替),这用来标识创建打开事务的会话。我们可以通过SPID判断事务是真的孤立还是只是长时间运行的,通过查询sysprocesses视图(在SQL Server 2000里)或者SQL Server 2005及后续版本里的sys.dm_exec_sessions和sys.dm_exec_connections的动态视图,如代码7.8所示。注意sysprocesses视图在SQL Server 2005及后续版本还是可用的,保持向后的兼容性。在运行代码7.8时,在每个查询里,直接用你看到的会话值替换session_id值(我们注释了几列,只是为了简化输出的可读性)。
1 USE master
2 GO
3 SELECT spid ,
4 status ,
5 -- hostname ,
6 -- program_name ,
7 -- loginame ,
8 login_time ,
9 last_batch ,
10 ( SELECT text
11 FROM ::
12 fn_get_sql(sql_handle)
13 ) AS [sql_text]
14 FROM sysprocesses
15 WHERE spid = 53
17 USE FullRecovery
18 GO
19 SELECT s.session_id ,
20 s.status ,
21 -- s.host_name ,
22 -- s.program_name ,
23 -- s.login_name ,
24 s.login_time ,
25 s.last_request_start_time ,
26 s.last_request_end_time ,
27 t.text
28 FROM sys.dm_exec_sessions s
29 JOIN sys.dm_exec_connections c ON s.session_id = c.session_id
30 CROSS APPLY sys.dm_exec_sql_text(c.most_recent_sql_handle) t
31 WHERE s.session_id = 53
(代码7.8:使用DMV来识别孤立的还是长时间运行的事务)
如果回话是runnable,running或suspended状态,那么可能问题的根源是长时间运行,而不是孤立的事务。但是,只有进一步的调查才能确认。很有可能刚才的事务失败且连接重置,使用连接池,当前运行的语句不是打开事务所关联的。
在SQL Server 2005和后续版本,我们可以使用sys.dm_tran_session_transactions和sys.dm_tran_database_transactions对打开的事务收集信息,包括事务开始事件,打开事务使用的日志数,以及日志空间使用字节数,如我们刚才代码7.1所见。代码7.9展示了一个简单的版本,带有范例输出。
(代码7.9:收集打开事务的信息)
除非应用程序设计来检查,处理孤立的事务,清除事务的唯一方法是KILL会话,它会造成事务回滚,和连接中断一样,在下一次日志备份期间,允许日志里的空间是可以被重用的。但是,回滚的执行后果必须要理解的。
其他引起日志增长的可能原因
除了刚才提到的原因之外,还有其他一些问题阻止日志里空间重用,导致日志过度增长。这里我会谈其中的一些,这个问题的更多信息,可以看下Gail Shaw的文章,为什么我的事务日志满了?
REPLICATION
在事务复制期间,日志读取代理的任务是读取事务日志, 查找关联修改的日志记录,复制到订阅者(例如,“待定的复制”)。一旦修改被复制,会标记日志为“已复制”。缓慢或延迟的日志读取活动会导致记录剩为“待 定的复制”很长时间,在此期间它们还是活动日志的一部分,因此母VLF不能被截断。对于通过变更数据捕获( Change Data Capture (CDC))功能需要的日志记录也有类似的问题存在。
不管任何情况,sys.databases的 log_reuse_wait_desc列会显示REPLICATION作为问题根源。在事务磁盘阵列的输出性能里,这个问题本身也暴露了瓶颈。尤其是, 在并发写加载下的延迟读取操作。写入日志文件会持续发生,但用日志读取代理相关的和日志备份文件读取的读操作也要持续的。同一时间有持续的读和写发生,取 决于系统中的日志活跃级别和活动日志部分的大小,会导致磁头随机的I/O活动,因为磁头需要改变位置来读取活动日志的头,然后活动日志的尾。我们可以使用性能监视器(PerfMon)里磁盘计数器 Physical Disk\Disk Reads/sec 和 Physical Disk\Disk Writes/sec来故障排除这类问题,看下SQL Server的故障排除的免费电子书的第2章来进一步了解这个问题的细节:https://www.simple-talk.com/books/sql-books/troubleshooting-sql-server-a-guide-for-the-accidental-dba/
这些复制等待问题的故障排除的第一步是识别日志读取器,SQL 代理作业是否正常运行。如果不是的话,尝试启动它们。如果启动失败,你要找出为什么。
如果作业是运行的,但是复制一直等待,事务日志快速增长,你需要找到一些方法让相关的日志标记为“已复制”,这样的话它们的母VLF可以被重用。遗憾的是,没有完美的解决方案来避免复制或在CDC环境里的副作用,但你可以尝试下面方法中的一种。
在事务日志复制的情况下,使用sp_repldone命令来标记在日志读取器上当前正等待的所有日志记录为已复制,但还是需要重新初始化订阅者,CDC的话,这个命令不会解决事务日志增长的问题。
停用CDC或复制,进行数据的人为同步。停用CDC或复制后,事务日志中的待定复制的日志记录不会是待定,在完整或大容量日志恢复模式里的下次日志备份,或简单恢复模式里的检查点操作,会清除掉。但是,换来的代价是对于CDC环境需要数据的人为的同步,对于复制需要人为初始化订阅者,如果这个功能加回到数据库的话。
记住直接切换到简单恢复模式,希望能截断日志,是不行的,因为复制和CDC2个均不支持简单恢复模式,还是继续需要日志读取器直到日志读取器的SQL代理处理完成处理。
快照复制架构改变问题
在SQL Server 2005里使用快照复制有一个已知的问题,当架构修改时,它会导致应该标记为复制的架构修改没被标记。这个问题可以看下这个文章的解决方法:http://blogs.msdn.com/b/sqlserverfaq/archive/2009/06/01/size-of-the-transaction-log-increasing-and-cannot-be-truncated-or-shrinked-due-to-snapshot-replication.aspx
ACTIVE_BACKUP_OR_RESTORE
当log_reuse_wait_desc column列显示为ACTIVE_BACKUP_OR_RESTORE作为当前等待描述,长时间运行的数据库完整或差异备份是最有可能导致日志重用问题。在数据库完整或差异备份期间,备份过程会延迟日志截断,这样的话事务日志的活动部分会被包含为完整备份的一部分。在备份操作没完成期间,允许修改到数据库的页,当备份用WITH RECOVERY恢复时,可以让数据库恢复到一致的状态。如果这样的等待造成持续的问题,你会需要调查下优化备份过程的方式,例如提高备份性能(提供备份压缩)或者提高硬盘I/O系统的性能。
DATABASE_MIRRORING
当log_reuse_wait_desc column列显示为DATABASE_MIRRORING,作为当前等待描述,异步数据库镜像操作可能导致日志重用问题。
在异步镜像里,主上的事务只有一旦提交,相关的日志记录才会传输到镜像数据库。对于异步数据库镜像,主的日志不能截断直到日志记录已传输。当镜像问题发生时,主上大量的日志记录会保持为活动日志的一部分,阻止日志空间重用,直到复制到镜像完成。
对于异步数据库镜像,如果镜像不可用我们会看到DATABASE_MIRRORING,归因于断开或非常慢的连接,或镜像会话的挂起。对于异步数据库镜像,在正常操作和连接问题期间,我们会看到这个值。
在这个情况下,首先我会检查下受影响数据库的镜像会话状态。如果它们没有正确同步,那么你需要在主和镜像之间故障排除失败连接的原因。数据库镜像一个最常见的原因,当证书用来保证安全终端时,是证书过期,需要重新颁发证书。进一步讨论镜像连接问题处理已经不是这个文章的讨论范围,除非数据库已经正常同步,那么日志记录会发送到镜像,在主上事务日志的活动部分会继续增长,不能截断直到中断镜像配置。
如果在主上的日志率大大超过可以传送到镜像的日志率,那么主上的日志会快速增长。如果镜像服务器用来做报表,通过创建快照,对镜像验证磁盘I/O配置没有饱和,通过刚才提高的性能监视器里硬盘计数器。如果这是问题所在,停止镜像服务器的服务器可以临时解决问题。如果问题是严格的大量事务,数据库没有运行在SQL Server 2008或更高,那么升级可以解决问题,因为可以使用SQL Server 2008或更高版本的日志流压缩。
最好的方法是判断镜像问题的原因并解决它。例如,调优生成大量日志记录的操作,例如大容量加载数据,或者重组索引,在操作期间可以减少对系统的影响。
处理事务日志满错误
最坏的情况,事务日志管理不当或突发、快速的日志增长会造成事务日志增长,最后吞食完硬盘上所有可用空间。到这个时候就不能增长了,你会遇到9002错误,事务日志满错误,数据库会变成只读。
尽管这个问题很紧迫,冷静面对很重要,避免这类接下来会提到的”无意识“的解决方法,处理不当或做不该做。显然当前的问题是让SQL Server可以继续写日志,通过生成更多可用空间。如果起因是缺少日志备份,第一个要做的是重新运行代码7.1;如果log_reuse_wait_desc列返回值是 Log Backup,那么和可能这是问题原因。一个在MSDB数据库里对backupset表的查询,如代码7.10所示,会确认是否要在数据库上进行一次日志备份,还有上一次日志备份的时间。
1 USE msdb ;
2 SELECT backup_set_id ,
3 backup_start_date ,
4 backup_finish_date ,
5 backup_size ,
6 recovery_model ,
7 [type]
8 FROM dbo.backupset
9 WHERE database_name = 'DatabaseName'
(代码7.10:哪个备份已做,什么时候做的)
在type列,D代表数据库备份,L代表日志备份,I代表差异备份。如果没有日志备份,或者它们并不频繁,那么你最好的做法是进行一次日志备份(这里假定数据库运行在完整或大容量日志恢复模式)。希望,这个能释放日志里的实在空间,然后你可以进行合适的日志备份计划和日志增长管理策略。
如果因为某些原因不能进行日志备份,例如磁盘空间不足,或者进行日志备份的时间超过可接受的问题解决时间,那么,取决于对问题数据库的灾难恢复策略,或许可以通过临时切换到简单恢复模式来强制日志截断,这样在检查点的时候日志中不活动的VLF会被截断。然后你可以切换回完整数据库恢复模式,进行新的完整数据库备份(或差异备份,这里假定先前已经有一次完整备份)来重新开始用于时间点恢复的日志链。当然,你还是充分调查问题,来保证空间不会再次直接吞食完。还有记住这点,刚才讨论过的,如果阻止空间重用的问题不是日志备份,那么这个技术就无效了,因为这些记录会保留在活动日志里,阻止截断。
如果缺少日志备份不是问题,或者进行完日志备份不能解决问题,那么调查原因可能会花更多的时间。最快和最简单的方法是在日志硬盘上增加更多的空间。这表示要清理掉其他文件,或者增加当前日志硬盘的容量,或者在不同的硬盘列里增加额外日志文件,但这会占用你一点喘息的空间,你需要让数据库摆脱只读模式,然后进行一次日志备份。
如果日志备份释放空间失败,你要找出什么阻止了日志里的空间重用。调查下sys.databases(代码7.1)来找出什么阻止了日志空间重用,采取合适的行动,如刚才缺少日志空间重用部分介绍的。
如果这个啥都没透露,你需要进一步调查找出什么操作造成过度日志导致日志增长,如事务日志过度增长部分介绍的。
最后,解决了任何空间重用问题,很可能我们的日志文件会在磁盘上占用很大的空间。作为一次性的测量,例如假定我们采取措施保证日后日志增长有妥善的管理(下一部分就会谈到),是可以使用DBCC SHRINKFILE来回收臃肿事务日志文件使用的空间。在第8篇我们会提供如何做的例子。
我们要么指定收缩日志的文件target_size,要么指定0位目标大小,让日志收缩的尽可能小,然后立即使用ALTER DATABASE来调整到合适的大小。后者是推荐的方法,它会最小化日志文件的碎片。碎片问题是你应该从不定期进行的DBCC SHRINKFILE任务的主要原因,因为它只用来控制日志大小;我们会在下个部分详细讨论这个。
处理不当和不该做的事
遗憾的是,在网络上搜索”事务日志满“会返回大量论坛的帖子,博客文章,甚至很多复制于SQL Server网站的文章,那些建议矫正的方法,坦白说,很危险。我们在这里会谈其中一些流行的建议。
分离数据库,删除日志文件
这个方法,你清理了所有用户的数据库,分离数据库(或者关闭它),删除日志文件(或重命名),然后重新附加数据库,会引起新的日志文件创建,它的大小由model数据库决定。这可以说是处理完整事务日志的最可怕的方式。它会造成数据库启动失败,数据库为RECOVERY_PENDING状态。
取决于数据库在日志删除时是否正常关闭,在数据库正常部分的恢复阶段,数据库可能不能进行撤销和重做操作,因为事务日志已经丢失,不能返回数据库为一致的状态。当日志文件丢失时,数据库需要事务日志来进行故障恢复,数据库不能正常启动,只能从最近的可用备份里恢复数据库,这就会导致数据丢失。
创建,分离,附加,修复可疑数据库
在特定情况下,可以黑入现存的数据库的配置,允许事务日志重建,但这会破坏数据库里现有数据库的完整性。这类操作是,最好是最后实在绝对没法恢复数据库数据了,这是我们这个系列文章不推荐的做法。至于如何尝试黑入数据库来看已删除的事务日志,可以看下Paul Randal的文章:创建,分离,附加,修复可疑数据库。
强制日志文件截断
在SQL Server 2000 和2005,BACK LOG WITH TRUNCATE_ONLY是SQL Server支持的强制截断事务日志的方法,在数据库运行在完整或大容量日志模式。使用这个命令实际不会做日志内容备份副本;在截断VLF里的记录会忽略。因此,不像正常日志备份,你在破坏你的LSN链,你只能恢复数据库到先前任何日志备份里的时间点。还有,即使数据库设置为完整恢复模式,实际上,从那个点开始,会运行在自动截断模式,在检查点会继续截断不活动的VLF。为了让数据库运行在完整恢复模式,重新开始LSN链,你需要进行一次完整(或差异)备份。
没有意识到对灾难恢复的影响的人们才会经常使用这个命令,在SQL Server 2005里已经废弃,从SQL Server 2008开始已经移除这个命令了。遗憾的是,这个技术更阴险的版本,还是继续被支持,取而代之这个命令,那就是BACKUP LOG TO DISK='NUL',NUL是忽略任何数据写入的“虚拟文件”。这个技术的真正扭曲是,不像BACKUP LOG WITH TRUNCATE_ONLY,SQL Server不管日志记录,直接忽略。就SQL Server而言,进行日志备份后,日志记录会在备份文件里安全存储,这样的话,LSN链是完整的,在活动日志里的不活动VLF可以安全截断。任何接下来,惯例的日志备份会成功,但从故障恢复的角度来说是无用的,因为日志备份文件丢失的话,数据库只能恢复到上次标准日志备份的时间点,在BACKUP LOG TO DISK='NUL'命令发出前。
不要使用这里的任何技术。强制日志截断的正确方法是临时切换数据库导简单恢复模式,如前所述。
计划事务日志收缩
如在处理事务日志满错误部分讨论的,事务日志在很少情况下是因为管理不当造成的,日志增长正被活动管理,使用DBCC SHRINKFILE来回收事务日志占用的空间是个可以接受的操作。
但我们绝不能把日志收缩作为日常,计划维护操作的一部分。原因是我们每次收缩日志,它会为接下来的事务立即再次增长来存储日志记录。如在日志大小和增长部分讨论的,事务日志不能利用即时文件初始化,因此所有日志增长引发SQL Server需要分配的存储空间填零操作。另外,如果我们依赖事务日志自动增长(下部分会谈到),在日志文件了会聚集更多的VLF,这个日志碎片会影响任何需要读取这个日志文件的进程性能,如果碎片实在太多,也会影响到数据修改性能。
对于事务日志文件的最佳做法是预先设置好它的合适大小,这样的话正常情况下就不会增长。然后,监视它的使用率来决定是否需要人为增长,允许你决定合适的增长大小且决定要添加到日志文件里的VLF的大小和个数。在第8篇我们会具体讨论。
妥当的日志管理
没有任何意想不到的操作或问题而导致不正常的日志增长(复制问题,未提交的事务等等),如果事务日志关联的数据库运行在完整恢复模式,还一直增长,其实只有2个原因:
日志文件大小太小,支持不了当前数据库所发生的数据修改。
日志备份的频率不够,满足不了日志文件里快速空间重用。
最好的做法,如果你不能通过减少它们之间的时间来增加日志备份的频率,当在加载的时,可以人为增加日志文件大小而不是让它自动增长,然后恢复原来大小。有大的我们人为增长的事务日志文件,但有最小化数量的VLF并不是个坏事,即使大部分时间日志文件有空余空间。我们会在第8篇详细讨论这个。
对于SQL Server数据库的操作,事务日志非常重要,还有在灾难事件里能最小化数据丢失风险。在日志疯狂增长的情况里,甚至满了,DBA需要快速诊断并解决问题,同时要保持冷静也非常重要,避免不深思熟虑的反应,例如强制日志截断,还有计划定期的日志收缩,这只会弊大于利。
非常感谢SQL Server故障排除的作者Jonathan Kehayias,电子书也是可以下载的,为本文提供了大量参考。
也感谢您这么耐心围观完这篇文章,我真是挤牙膏一样,熬了一个星期才能出一篇文章,感谢您的关注!!!
注:此文章为WoodyTu学习MS SQL技术,收集整理相关文档撰写,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错请点击下右下角的推荐,有了您的支持才能激发作者更大的写作热情,非常感谢!