自SQL Server 2005起,我們有了READ COMMITTED SNAPSHOT ISOLATION level (RCSI) 和SNAPSHOT ISOLATION level (SI)兩個事務隔離級別。當你使用這些事務隔離級別時,讀操作(SELECT語句)在讀的時候不需要S鎖(共享鎖),寫操作(UPDATE,DELETE語句)會對記錄進行版本控制,這些改變會寫入TempDb。它們就會生成一個版本鏈,記錄的最新版本(存在數據庫里的數據頁里)指向存在TempDb里的頁,下圖可以幫助我們理解這個情況。
為了使這個機制有效,SQL Server需要在數據庫內部的數據頁上的每條記錄,增加14 bytes長的指針。這就是說,每條記錄增加了14 bytes的長度?;蛟S你已經知道,當你使用定長數據類型時,SQL Server內部的記錄長度不能超過8060 bytes。這就意味著,當你啟用RCSI/SI隔離級別時,會導致記錄超過現有的8060 bytes。我們來看一個簡單的例子:
1 USE master 2 GO 3 4 -- Create a new database 5 CREATE DATABASE VersionStoreRestrictions 6 GO 7 8 -- Enable RCSI 9 ALTER DATABASE VersionStoreRestrictions SET READ_COMMITTED_SNAPSHOT ON10 GO11 12 -- Use it13 USE VersionStoreRestrictions14 GO15 16 -- Create a table where each record is 8047 bytes large17 CREATE TABLE TableB18 (19 Column1 CHAR(40),20 Column2 CHAR(8000)21 )22 GO
從代碼里我們可以看到,這里我創建了帶2個CHAR列,總長為8040 bytes的表。SQL Server為每條記錄內部需要至少7 bytes的開銷。這里數據頁上的1條記錄需要8047 bytes。因為我們在數據庫級別啟用了RCSI數據隔離級別,SQL Server需要增加額外的14 bytes作為行版本指針(Row Version Pointe),這就把表里的每條記錄長度擴展到8061 bytes。對于SQL Server來說,這就意味著每條記錄太長了(多出1 byte)。我們在表里插入1條記錄看看:
1 -- Insert a initial row2 INSERT INTO TableB VALUES (REPLICATE('A', 40), REPLICATE('A', 8000))3 GO
現在當你嘗試去更新這個記錄(SQL Server嘗試在TempDb里對這條記錄進行版本控制),SQL Server會報下列錯誤:
1 UPDATE TableB2 SET Column1 = REPLICATE('B', 40)3 GO
這個錯誤信息非常有意義,因為數據庫上下文信息是錯誤的(SSMS顯示你還在master數據庫)。但是當你在UPDATE語句加上表架構時,你就能拿回實際的錯誤信息:
1 UPDATE VersionStoreRestrictions.dbo.TableB2 SET Column1 = REPLICATE('B', 40)3 GO4
哇噢,這是個內部錯誤,因為SQL Server使用的緩存只有8060 bytes 大,現在我們嘗試在那個緩存里保存8061 bytes——瞧!這在SQL Server內部是個bug!你可以在自SQL SERVER 2005以后的版本里驗證這個BUG,也就說,這個BUG已經存在好幾年了(SQL Server 2012已經修正這個BUG,但在頁里面的確存儲了預期的8061 bytes,我測試的版本是SQL Server 2008R2)。
當你對數據庫啟用RCSI/SI數據隔離級別時,你就要留意這個BUG了,因為這意味這RCSI/SI在任何情況下都無效了。當在你的數據庫里有1個表超過8046 bytes限制,那你真的是有麻煩了!通過這個危險的BUG(nasty bug),你也會理解,知道SQL Serve內部架構和內部如何存儲數據是多么重要??!
新聞熱點
疑難解答