Sql Server 2012新特性 Online添加非空欄位.


我們都知道,Sql Server在一個數據量巨大的表中添加一個非空欄位是比較費心的,缺乏經驗的DBA或是開發人員甚至可能魯莽地直接添加導致阻塞相應業務,甚至可能因為資源欠缺造成實例的全局問題.當然這都是Sql 2008R2及以前版本的情況.在SQL2012中采用了新的實現方式.這里我將對比相應的實現方式給大家做個介紹.並簡單說明Sql Server早期版本添加非空列的方法.

添加非空欄位的實現方式

早期版本(Sql Server2008R2及以前)添加非空欄位(要求有默認值)是對表中的所有數據行依次修改調整

我們通過一個簡單的實例來看下

Sql 2008R2 SP2 Code

Create database tadnull
go
use tadnull
go
create table t2(id int not null identity (1,1),dystr varchar(20),fixstr char(30));
go
set nocount on 
declare @batchSize int
set @batchSize=1000
declare @i int
set @i=0
while(@i<20000)
begin
  if (@i%@batchSize=0)
    begin
      if (@@TRANCOUNT>0)COMMIT TRAN
      BEGIN TRAN
    end
  insert into t2(dystr,fixstr)values('aaa'+str(RAND()*100000000),'bbb'+str(RAND()*100000000))
  set @i=@i+1
end

 if (@@TRANCOUNT>0)COMMIT TRAN
dbcc ind(tadnull,t2,1) -----find a datapage pageid 21
    
    dbcc traceon(3604)
    dbcc page('tadnull',1,21,3)-----view the datapage 21

通過DBCC PAGE我們打印其中的一個數據頁進行分析.

可以看到圖1-1當前數據頁的最后修改的日志記錄為m_lsn = (28:69:279)

數據頁中第一行數據(slot 0)頁偏移量0x60,行長度為58

                                                 圖1-1

添加非空欄位code

alter table t2 add tt int not null default '10'
dbcc page('tadnull',1,21,3)-----view the datapage 21
select b.* from sys.system_internals_partitions a
    join sys.system_internals_partition_columns b on a.partition_id = b.partition_id
    where a.object_id = object_id('t2');-----查看行數修改情況    

可以看到圖1-2添加非空欄位后,字節數據頁最后修改的LSN號由m_lsn = (28:69:279)變為了

    m_lsn = (43:432:186),數據頁中首行長度也由58變為了62正好是一個int數據類型長度.

當然我們也可以看到添加過程中會有大量的key鎖出現.圖1-3

(可以通過DMV查看,或者Xevents,trace profiler等探究)

從特定的DMV中,我們看到新加列的修改行數為200000,為表的數據行數,這也反應的數據修改為逐行修改.圖1-4

                                               圖1-2

                                        圖1-3

                                       圖1-4

由此我們可以看到,在sql2008R2及以前版本中添加非空欄位會對表中數據逐行操作.操作執行成本很高.

接下來我們再看看sql2012采用的新的實現方式.

Sql 2012 SP1 code

Create database tadnull
go
use tadnull

create table t2(id int not null identity (1,1),dystr varchar(20),fixstr char(30));
go
set nocount on 
declare @batchSize int
set @batchSize=1000
declare @i int
set @i=0
while(@i<200000)
begin
  if (@i%@batchSize=0)
    begin
      if (@@TRANCOUNT>0)COMMIT TRAN
      BEGIN TRAN
    end

  insert into t2(dystr,fixstr)values('aaa'+str(RAND()*100000000),'bbb'+str(RAND()*100000000))
  set @i=@i+1

end
 if (@@TRANCOUNT>0)COMMIT TRAN

     
dbcc ind('tadnull','t2',1)--------find a data page (pageid 120)

dbcc traceon(3604)
dbcc page('tadnull',1,120,3)

通過DBCC PAGE我們打印其中的一個數據頁進行分析.

可以看到圖2-1當前數據頁的最后修改的日志記錄為  m_lsn = (33:155:157)

數據頁中第一行數據(slot 0)頁偏移量0x60,行長度為58

                                            圖2-1

添加非空欄位code

alter table t2 add tt int not null default '10'
dbcc page('tadnull',1, 120,3)-----view the datapage 120
select b.* from sys.system_internals_partitions a
    join sys.system_internals_partition_columns b on a.partition_id = b.partition_id
    where a.object_id = object_id('t2');-----查看行數修改情況    

通過圖2-2可以看出,頁的最后修改的LSN記錄m_lsn = (33:155:157)行長度 Length 58與添加欄位前一致!這與sql2008R2截然不同.

通過圖2-3可以看出新增欄位的修改行數為0,這反應了添加新行的修改非逐行修改.

由以上兩個圖我們可以推斷出添加新的非空欄位並非在新數據頁上逐一操作,而是元數據級的操作.

感興趣讀者可以通過Xevent,Trace Profiler的鎖捕捉來進一步驗證.

                                               圖2-2

                                              圖2-3

在添加完后的打印數據頁120的內容中我們可以看到圖2-4中欄位號4名稱dd已經被添加,但其物理長度為0,及行中欄位值未添加到此數據頁上

                                       圖2-4

 

問題來了,那么這個欄位就永遠不會去填充數據頁而只是標識嗎?

我們簡單修改一下120頁中的一條數據然后再打印此頁內容

修改CODE

update t2 set dystr='shanksgao1111' where id=1
checkpoint
dbcc page('tadnull',1,120,3)

由於我得屏幕不能完全打印單頁中我們所需的內容,此頁沒截圖,直接輸出.截取其中需要信息

可以從紅色的字體中看到,此頁上的LSN變化為m_lsn = (157:336:2),第一行長度(id=1)變為了62(58+4)雖然我只是修改了變長欄位dystr但變長長度和以前一樣.但新添加的非空欄位的值也添加到此頁上來. Slot 0 Column 4 Offset 0x26 Length 4 Length (physical) 4.

******************************************打印數據頁內容******************************************

 

m_freeData = 7640                   m_reservedCnt = 0                   m_lsn = (157:336:2)

 

m_xactReserved = 0                  m_xdesId = (0:0)                    m_ghostRecCnt = 0

 

m_tornBits = -1896544491            DB Frag ID = 1                     

 

Allocation Status

 

GAM (1:2) = ALLOCATED               SGAM (1:3) = NOT ALLOCATED         

 

PFS (1:1) = 0x64 MIXED_EXT ALLOCATED 100_PCT_FULL                        DIFF (1:6) = CHANGED

 

ML (1:7) = NOT MIN_LOGGED          

 

Slot 0 Offset 0x1d9a Length 62

 

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS

 

Record Size = 62                   

 

Memory Dump @0x000000002F5EBD9A

 

0000000000000000:   30002a00 01000000 62626220 20203838 39373839  0.*.....bbb   889789

 

0000000000000014:   30202020 20202020 20202020 20202020 20200a00  0                 ..

 

0000000000000028:   00000400 0001003e 00736861 6e6b7367 616f3131  .......>.shanksgao11

 

000000000000003C:   3131                                          11    

 

Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4

 

id = 1                             

 

Slot 0 Column 2 Offset 0x31 Length 13 Length (physical) 13

 

dystr = shanksgao1111               

 

Slot 0 Column 3 Offset 0x8 Length 30 Length (physical) 30

 

fixstr = bbb   8897890                                                  

 

Slot 0 Column 4 Offset 0x26 Length 4 Length (physical) 4

dd = 10                        

******************************************打印數據頁內容******************************************

 

通過上面的實例我們可以看到,在sql2012中,當我們添加一個非空欄位時,實際對相應數據頁上的操作並非逐一修改添加.因此執行速度非常快,對線上業務影響很低.但由於其並非實際填充數據頁使的Sql Server在應用,維護數據頁時進行額外的工作.對此Sql Server采用了較為平滑的處理方式.當更新某(些)行時,再進行頁中數據填充,降低了影響粒度.更友好地滿足了線上業務的需求.

注:此方式添加新的非空欄位時不包括Blob數據類型.

   添加行版本戳(rowversion/ timestamp)不支持.

 早期版本調整方式

我們可以看到sql2012的改進,但早期版本如果維護該如何操作?

早期版本中添加非空欄位針對數據量較小的表還好,但是針對大表由於其添加非空欄位的實現特性,致使阻塞發生的幾率明顯加大.因此針對大表我們必須謹慎進行.

a 如果有能調整的時間窗口則可以先預估下執行時間(相似環境中測試)然后時間窗口中操作.

b 如果沒有可供調整的時間窗口而需求又看似急茬,這樣我們可以采用如下步驟循序漸進的調整降低其影響的粒度.

添加非空欄位

1 添加一個帶默認值得可為空欄位

alter table t2 add col5 int default 100

2 添加一個約束使其col5不可為空,但不檢查現有數據

ALTER TABLE t2 WITH NOCHECK

ADD CONSTRAINT CK_NOTNULL CHECK (col5 IS NOT NULL)

3 輪詢批量修改現有數據,更改現有col5為默認值

WHILE 1=1

BEGIN

  BEGIN TRAN

   UPDATE TOP(2000) t2 SET col5 = 100 WHERE col5 IS NULL

    IF @@rowcount < 2000

    BEGIN

      COMMIT TRAN

      BREAK;

    END

  COMMIT TRAN

END-------------限制數據量修改,盡量避免鎖升級

 

添加行版本戳

由上面的內容我們已經知道添加行版本戳(rowversion)時無法使用新特性的,那面對大表DBA該如何操作呢?如果有相應時間窗口那是最好,如果沒有,DBA需要和研發溝通利用sql server相應知識變向調整.

這里我借鑒與sql 2012 online添加非空欄位時類似的實現方式但需要研發同事配合

大表中添加行版本戳

1 創建一個與原表SCHEMA完全一致的表

2 創建一個視圖為原表union all 新表

3 需研發同事配合調整訪問實現方式

 a 讀訪問從視圖

b 插入操作插入到新表

c 修改操作原表移動到新表

4 采用批量輪詢修改移動

5 移動完畢后調整表名稱(原表改隨意名,新表調整為原表名,告知研發調整,刪除視圖與原表)

 

如果研發無法配合調整,如此情況下就比較麻煩了.此時DBA需要一個時間窗口.

為了更迅捷地將相應欄位加上減小阻塞時間我們可以/盡量采取如下一系列的方式來縮短窗口時間.

1 選擇實例中相關對象負載最低的時間段進行

2 為執行操作提供盡量多的資源來確保其盡快完成.

  如 調整加大buffer pool的大小

     操作前騰出足夠的內存供其使用

Checkpoint

Dbcc dropcleanbuffers-----注意這些操作需保證實例應用在可接受的情況下進行

3 執行的會話中提前獲取表級排他鎖.(由於添加行版本戳本身也是逐行添加,會話中提前獲取表的排他鎖可以減小總共獲取鎖的次數進而加快執行速度) 

 begin tran ttt

    update t2 with(tablockx) set dystr='aaa' where 1=0

    /*調整代碼*/

    alter table t2 add rv rowversion

    commit tran ttt

 

 

 

 


免責聲明!

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



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