[SQL Server]内存缓存数据写入磁盘(三)延迟持久性Delayed Durability(和魔鬼交易)

[SQL Server]内存缓存数据写入磁盘(三)延迟持久性Delayed Durability(和魔鬼交易)
发表于 2019-09-13  |  分类于 后端  |  没有评论
Delayed Durability是SQL Server 2014的新功能,在某些Transaction log负载较大的情境中,如果愿意延迟一点ACID灵魂中的持久性(Durability),同时也有接受可能的Data Loss风险准备,也许可以用延迟持久性和魔鬼交换Transaction log写入性能。


上一篇进行间接检查点(Indirect CheckPoint)之后,我们从性能角度观察了等候时间统计的DMV(sys.dm_os_wait_stats),由于第二篇的测试情境是大量数据写入,发现了大量WRITELOG的WaitType!
?

前面我们曾讨论到,SQL Server为了确保完整性(Atomicity)及持久性(Durability),一个事务的Commit完成,是将写入Buffer Pool中的”Log” Flush到Disk上才算完成,也就是write-ahead log (WAL)。
之前曾有个客户的I/O瓶颈是在Transaction Log写入磁盘,在SQL Server 2014多了一种延迟持久性的作法,概念有点像我们AP使用异步方式写LOG,事务确认将数据及纪录写到Buffer后,事务不再等到Transaction log写入磁盘才算整个commit。
?
 
使用警语:
Delayed Durability作法是用ACID的100%持久性来和Log I/O性能作交换,就像一种和魔鬼的事务,德国民间传说中,浮士德(Faustus)用了灵魂换取了魔鬼的合约,重新拥有了青春和享乐。
?
测试Delayed Durability会分别以完整持久性(Full) vs 延迟持久性(Delayed)在几种特定事务活动来比较性能的差异。
    •    大量单笔事务写入
    •    整批事务写入
    •    整批事务更新
另外我们也尝试人工、弹性及数据库备份来触发及观察log Flush。


建立数据库并建立扩充事件观察Transaction log flush活动
1.建立测试数据库、复原模式为完整并进行完整备份
CREATE DATABASE [FlushDiskDb]
CONTAINMENT = NONE
ON  PRIMARY
( NAME = N‘FlushDiskDb‘, FILENAME = N‘C:tempdbFlushDiskDb.mdf‘ , SIZE = 8092KB , FILEGROWTH = 1024KB )
LOG ON
( NAME = N‘FlushDiskDb_log‘, FILENAME = N‘C:tempdbFlushDiskDb_log.ldf‘ , SIZE = 8092KB , FILEGROWTH = 10%)
GO

USE [master]
GO
ALTER DATABASE [FlushDiskDb] SET RECOVERY FULL WITH NO_WAIT
GO

BACKUP DATABASE [FlushDiskDb] TO  DISK = N‘C:tempdbFlushDiskDb.bak‘ WITH NOFORMAT, NOINIT,  
NAME = N‘FlushDiskDb-完整 数据库 备份‘, SKIP, NOREWIND, NOUNLOAD,  STATS = 10
GO
数据库准备好了!
?
2.建立扩充事件观察log Flush活动
CREATE EVENT SESSION [logFlushEvent] ON SERVER
ADD EVENT sqlserver.databases_log_flush(
    ACTION(package0.event_sequence,sqlserver.is_system))
ADD TARGET package0.event_file
(
    SET FILENAME = N‘C:tempdblogFlushEvent.xet‘
)
WITH (MAX_DISPATCH_LATENCY = 1 SECONDS)  

ALTER EVENT SESSION [logFlushEvent] ON SERVER STATE=START
扩充事件也准备好了!
SSMS管理工具 > 管理 > 扩充事件 > 工作阶段
?

写入前数据库log状态
use FlushDiskDb
DBCC LOGINFO
只有4个VLF
?
DBCC SQLPERF(logspace)
Log Size: 8MB
?


执行大量的单笔事务写入(对照组)
USE [FlushDiskDb]
--建立数据表t1
IF (object_id(‘t1‘)) is not null
DROP TABLE t1;
create table t1
(
   c1 int identity,
   c2 varchar(30)
)

--纪录开始时间
DECLARE @START DATETIME = GETDATE();
--执行100,000笔写入
Declare @COUNT INT = 0;
WHILE (@COUNT < 100000)
BEGIN
SET @COUNT = @COUNT + 1
INSERT INTO t1
        VALUES (‘T‘ + CONVERT(VARCHAR, @COUNT))
END
--(4)打印执行时间
PRINT ‘execution time(millisecond):‘ + CONVERT(VARCHAR, DATEDIFF(ms, @START, getdate()))
执行时间: 25秒
?
查询log flush次数:
SELECT count(*)
FROM sys.fn_xe_file_target_read_file(‘C:tempdblogFlushEvent*.xet‘, null , null, null);
增加了10万次log flush,也就是有10万次磁盘活动。
?


写入后数据库log状态(对照组)
use FlushDiskDb
DBCC LOGINFO
45个VLF(好像有点太多,可以调整文件初始及成长allocate size,避免太过破碎)
?
数据库Log Size:
DBCC SQLPERF(logspace)
10万笔事务,Log将近增加了400MB
?


启用延迟持久性(DELAYED_DURABILITY)实验组
--数据库内所有的事务都启用延迟写入
ALTER DATABASE FlushDiskDb SET DELAYED_DURABILITY = FORCED

执行写入
--纪录开始时间
DECLARE @START DATETIME = GETDATE();
--执行100,000笔写入
Declare @COUNT INT = 0;
WHILE (@COUNT < 100000)
BEGIN
SET @COUNT = @COUNT + 1
INSERT INTO t1
        VALUES (‘T‘ + CONVERT(VARCHAR, @COUNT))
END
--(4)打印执行时间
PRINT ‘execution time(millisecond):‘ + CONVERT(VARCHAR, DATEDIFF(ms, @START, getdate()))
执行时间: 1.3秒
?
查询log flush次数:
SELECT count(*)
FROM sys.fn_xe_file_target_read_file(‘C:tempdblogFlushEvent*.xet‘, null , null, null);

10万笔事务写入,Log Flush事件只增加了1.3 万次?
写入速度从25秒到1.3秒,飞快的完成10万笔数据写入,接近20倍的速度获利,很像 BulkCopy的无敌速度,F1中的延迟刹车果然强大。
虽然延迟了Log flush到Disk的时间,但实际观察log fush事件,大约在80秒后,全部的log也都写入磁盘了。
*如果用Process Monitor观察,也可以发现磁盘写入的活动不再这么频繁。


手动执行Flush log
sys.sp_flush_log
观察Log Size
DBCC SQLPERF(logspace)
Log只增加86(490-404)MB
?


弹性启用延迟持久性
在线上事务数据库的环境下要启用延迟持久性需要过人的勇气,但我们也还可以有另一个平衡的选择,用弹性的方式在事务层级启用。
只要在事务commit时加上语法: COMMIT TRANSACTION WITH (DELAYED_DURABILITY = ON);
--允许数据库内事务使用延迟写入
ALTER DATABASE FlushDiskDb SET DELAYED_DURABILITY = ALLOWED


--纪录开始时间
DECLARE @START DATETIME = GETDATE();
--执行100000笔写入
Declare @COUNT INT = 0;
WHILE (@COUNT < 100000)
BEGIN
SET @COUNT = @COUNT + 1
BEGIN TRANSACTION
INSERT INTO t1
        VALUES (‘T‘ + CONVERT(VARCHAR, @COUNT))

COMMIT TRANSACTION WITH (DELAYED_DURABILITY = ON);
END
--打印执行时间
PRINT ‘execution time(millisecond):‘ + CONVERT(VARCHAR, DATEDIFF(ms, @START, getdate()))

执行时间: 1.7秒
依然在秒间处理完毕!
?


批次写入(insert into select )的比较
此时来源数据表已经有30万笔数据了
?
先建立两个空的数据表,待会让实验组及对照组写入!
IF (object_id(‘t2‘)) is not null
DROP TABLE t2;
create table t2
(
   c1 int,
   c2 varchar(30)
)
IF (object_id(‘t3‘)) is not null
DROP TABLE t3;
create table t3
(
   c1 int,
   c2 varchar(30)
)
实验组:
--强制延迟写入
ALTER DATABASE FlushDiskDb SET DELAYED_DURABILITY = FORCED

DECLARE @START DATETIME = GETDATE();
INSERT INTO t2 select * FROM T1
PRINT ‘execution time(millisecond):‘ + CONVERT(VARCHAR, DATEDIFF(ms, @START, getdate()))
执行结果:
?
 
对照组:
--关闭延迟写入
ALTER DATABASE FlushDiskDb SET DELAYED_DURABILITY = DISABLED

DECLARE @START DATETIME = GETDATE();
INSERT INTO t3 select * FROM T1
PRINT ‘execution time(millisecond):‘ + CONVERT(VARCHAR, DATEDIFF(ms, @START, getdate()))
执行结果:
?
都是0.4秒的秒杀,整批导入数据情境下,看起来差异不大。


批次更新比较(Batch Update)
实验组
--强制延迟写入
ALTER DATABASE FlushDiskDb SET DELAYED_DURABILITY = FORCED

DECLARE @START DATETIME = GETDATE();
UPDATE t2 set c2 = ‘FORCED‘
PRINT ‘execution time(millisecond):‘ + CONVERT(VARCHAR, DATEDIFF(ms, @START, getdate()))
执行结果:
?
 
对照组
--关闭延迟写入
ALTER DATABASE FlushDiskDb SET DELAYED_DURABILITY = DISABLED

DECLARE @START DATETIME = GETDATE();
UPDATE t3 set c2 = ‘DISABLED‘
PRINT ‘execution time(millisecond):‘ + CONVERT(VARCHAR, DATEDIFF(ms, @START, getdate()))
执行结果:
?
5倍获利


数据库备份是否会触发log flush活动?
马上备份!
?
看起来还是有触发log flush
?


小结:
    •    对于大量单笔事务有接近20倍的性能提升(1.3 vs 25秒)
    •    对于整批更新的事务有5倍的性能提升(0.9秒 vs 5.4秒)。
    •    如果数据库的复原模式只能选FULL,除了DELAYED_DURABILITY,也可以试试调校T-SQL语法或是AP架构让LOG的写入的数目压低。

很喜欢百敬老师说的性能调校两面刃,完整持久性与延迟持久性各有优缺点,使用Delayed Durability前,我们需要注意机器异常时所造成的Data loss,在可接受的特定事务中弹性使用是理想的选择。

Msdn警语:
如果不能容忍Data Loss,不建议使用Delayed Durability(If you cannot tolerate any data loss, you should not use delayed durability on your tables)

相关推荐