導數中的最小化日志記錄:測試和分析


測試和分析

依據上文件最小化日志的判斷邏輯,對常見的BULK INSERT和INSERT INTO...SELECT做測試和分析

  創建測試環境和基准

--創建表tb_source並插入10000條數據

use master

go

create database test;

alter database test set recovery bulk_logged with no_wait;

go

use test

go

create table tb_source (id int,val char(100));

insert into tb_source

select top(10000) ROW_NUMBER() over (order by sysdatetime()),'HeHe' from master..spt_values a,master..spt_values b

go

  創建基准表tb_benchmark,將tb_source的數據導出到文件,再導入到基准表。然后獲取最小化日志的統計做為測試基准。

create table tb_benchmark (id int,val char(100));

/**

CMD中導出數據:

C:\Users\Administrator>bcp test.dbo.tb_source out D:\ss\source.csv -S. -T -c

**/

--導入數據並並統計日志

create table tb_benchmark (id int,val char(100));

bulk insert tb_benchmark

from 'd:\ss\source.csv'

with (tablock) ;

SELECT COUNT(*)AS numrecords,

  CAST((COALESCE(SUM([Log Record LENGTH]), 0))

    / 1024. / 1024. AS NUMERIC(12, 2)) AS size_mb

FROM sys.fn_dblog(NULL, NULL) AS D

WHERE AllocUnitName = 'dbo.tb_benchmark' OR AllocUnitName LIKE 'dbo.tb_benchmark.%';

SELECT Operation, Context,

  AVG([Log Record LENGTH]) AS AvgLen, COUNT(*) AS Cnt

FROM sys.fn_dblog(NULL, NULL) AS D

WHERE AllocUnitName = 'dbo.tb_benchmark' OR AllocUnitName LIKE 'dbo.tb_benchmark.%'

GROUP BY Operation, Context, ROUND([Log Record LENGTH], -2)

ORDER BY AvgLen, Operation, Context;
Env&Benchmark

 

clipboard

從結果可以看出插入10000行,只產生了170條日志。沒有大於行大小(>104)的日志記錄。確定是最小化日志記錄。

1.bulk insert非空堆表

  前面的基准測試可以看到空堆表的insert,最小化日志記錄成功。非空的話,向基准表再導入一次數據。

clipboard[1]

從結果看,最小日志也是成立的。

2. bulk insert空聚集表

create table tb_btree1 (id int ,val char(100))

create clustered index cix_tb_btree1

on  tb_btree1 (id)

go

bulk insert tb_btree1

from 'd:\ss\source.csv'

with (tablock)
bulk insert into empty btree

 

clipboard[2]

這個最小化也是成立的,日志記錄多於空堆表的情況。

使用TF-610,而不使用tablock:

create table tb_btree2 (id int ,val char(100))

create clustered index cix_tb_btree2

on  tb_btree2 (id)

go

dbcc traceon(610)

bulk insert tb_btree2

from 'd:\ss\source.csv'

 

clipboard[3]

這種情況下不並完全是最小化日志記錄。從測試來看空聚集索引使用tablock產生的日志量會更少一些。這里為什么會有71行插入是完整日志記錄的呢?一個表至少有一個數據頁,向已有的數據頁上插入行是完整日志記錄,新分配的頁是最小日志記錄。至於為什么是71行,下面3.非空聚集表中一起分析。

3. bulk insert非空聚集表

  先創建表tb_btree,然后向其中插入60條記錄。觀察完整日志記錄的情況。

create table tb_btree (id int ,val char(100))

create clustered index cix_tb_btree

on  tb_btree (id)

go

declare @i int=0

while @i<60

begin

set @i=@i+1

insert into tb_btree values(@i,'HaHa')

end

 

clipboard[4]

插入60條記錄,有64條日志記錄。聚集索引日志60條,平均長度212(大於104)。

然后再向tb_tree中插入數據,對比日志情況。注意:導入數據我是從ID=61開始導入的。因為原表中有ID=[1,60]的行了,如果導入數據重復,會發生行移動和頁拆分等操作,這樣就會增加很多額外的日志,不便分析。

dbcc traceon(610)

bulk insert tb_btree

from 'd:\ss\source.csv'

with (ORDER(ID),FIRSTROW=61)

 

clipboard[5]

與前面對比,可以看出新插入9960行數據,只新增了913條日志。有意思的是,索引葉級頁插入(LOP_INSERT_ROWS&LCX_CLUSTERED)增加了11條,這11條是完整日志記錄的,其它行插入是最小化日志插入的。

這是為什么呢?

  這是因為原來tb_btree中只有一個數據頁,且只存放了60行數據。而這個數據頁上最多只能存放71行數據。也就是,在已經存在的數據頁中插入數據是完整日志記錄的,新分配的數據頁插入數據是最小化日志記錄的

為什么最多只能存放71條數據?

  直接通過DBCC PAGE查看對應數據頁最直觀。或者通過理論來計算:

  通過dbcc showcontig ('tb_btree') with tableresults得到行大小為111。假設頁上可以存N行數據,

  則:頁頭+偏移矩陣+行容量<=8KB-->96+2*N+111*N<=8192-->N<=71.65-->N=71

4. INSERT INTO...WITH(TABLOCK)...SELECT,向堆表中插入數據

--空堆表的情況,確認是最小化日志。

create table tb_heap1 (id int ,val char(100)) ;

INSERT INTO tb_heap1 with(tablock) select * from tb_source;

 

clipboard[6]

--非空堆表,將上面的數據再插入一遍即可。確認是最小化日志。

INSERT INTO tb_heap1 with(tablock) select * from tb_source;

 

clipboard[7]

5. INSERT INTO...SELECT...ODER BY(...),向聚集表中插入數據

--空的聚集表

create  table tb_cix (id int ,val char(100))

create clustered index cix_tb_cix

on  tb_cix (id)

go

dbcc traceon(610)

insert into tb_cix select * from tb_source order by id

 

clipboard[8]

同樣,已有的數據頁上是完整日志記錄,其它是最小化日志記錄。

--非空聚集表

--先插入70行數據,是完整日志記錄的。

create  table tb_cix2 (id int ,val char(100))

create clustered index cix_tb_cix2

on  tb_cix2 (id)

go

declare @i int=0

while @i<70

begin

set @i=@i+1;

insert into tb_cix2 values(@i,'HoHo')

end

 

clipboard[9]

--再從tb_source插入ID>70的9930行

dbcc traceon(610)

insert into tb_cix2 select * from tb_source where id>70 order by id

 

clipboard[10]

可以看到只新了一條完整日志的記錄,其它是最小化日志記錄的。

6. INSERT INTO...SELECT...ODER BY(...),並行導入聚集表

前文提到SQL 2008之后結合鍵范圍鎖,不會鎖定整個表,只鎖定某部分的鍵值區間,其它操作可以並行訪問此區間外的數據。

這里我打開三個session,同時插入三個區間的數據[1,1000],[3000,5000],[7000,9000]

create  table tb_cix3 (id int ,val char(100))

create clustered index cix_tb_cix3

on  tb_cix3 (id)

go

--session 1

dbcc traceon(610)

insert into tb_cix3 select * from tb_source where id between 1 and 1000 order by id

--session 2

dbcc traceon(610)

insert into tb_cix3 select * from tb_source where id between 3000 and 5000 order by id

option (querytraceon 2332)

--session 3

dbcc traceon(610)

insert into tb_cix3 select * from tb_source where id between 7000 and 9000 order by id

option (querytraceon 2332)

 

並行導入最好用ETL工具實現,特別是數據排序這一步。我在測試時,踩到一個坑:

當第一個insert 完成后,其它的insert的oder by會失效,造成無法最小化日志。除了第一個被執行的insert外,其它的執行計划中不會有SORT操作符。只好使用TF-2332,強制數據修改操作進行排序,也就是querytraceon 2332。還有就是數據量太少,都是瞬間完成,不好控制並發。

clipboard[11]

7. 在SSIS實際導數中的一個應用簡單例子

  這是實際項目中導數Destination的設置。一個聚集表導到另一個聚集表,實現了最小化日志。目標實例啟用了TF-610,FastLoadOptions設置為根據目標表聚集索引鍵進行排序。

clipboard[12]

clipboard[13]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM