概念介紹
開發(fā)人員喜歡在SQL腳本中使用WITH(NOLOCK), WITH(NOLOCK)其實是表提示(table_hint)中的一種。它等同于 READUNCOMMITTED 。 具體的功能作用如下所示(摘自MSDN): 1: 指定允許臟讀。不發(fā)布共享鎖來阻止其他事務修改當前事務讀取的數(shù)據(jù),其他事務設置的排他鎖不會阻礙當前事務讀取鎖定數(shù)據(jù)。允許臟讀可能產(chǎn)生較多的并發(fā)操作,但其代價是讀取以后會被其他事務回滾的數(shù)據(jù)修改。這可能會使您的事務出錯,向用戶顯示從未提交過的數(shù)據(jù),或者導致用戶兩次看到記錄(或根本看不到記錄)。有關臟讀、不可重復讀和幻讀的詳細信息,請參閱并發(fā)影響。 2: READUNCOMMITTED 和 NOLOCK 提示僅適用于數(shù)據(jù)鎖。所有查詢(包括那些帶有 READUNCOMMITTED 和 NOLOCK 提示的查詢)都會在編譯和執(zhí)行過程中獲取 Sch-S(架構穩(wěn)定性)鎖。因此,當并發(fā)事務持有表的 Sch-M(架構修改)鎖時,將阻塞查詢。例如,數(shù)據(jù)定義語言 (DDL) 操作在修改表的架構信息之前獲取 Sch-M 鎖。所有并發(fā)查詢(包括那些使用 READUNCOMMITTED 或 NOLOCK 提示運行的查詢)都會在嘗試獲取 Sch-S 鎖時被阻塞。相反,持有 Sch-S 鎖的查詢將阻塞嘗試獲取 Sch-M 鎖的并發(fā)事務。有關鎖行為的詳細信息,請參閱鎖兼容性(數(shù)據(jù)庫引擎)。 3: 不能為通過插入、更新或刪除操作修改過的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查詢優(yōu)化器忽略 FROM 子句中應用于 UPDATE 或 DELETE 語句的目標表的 READUNCOMMITTED 和 NOLOCK 提示。
功能與缺陷
使用WIHT(NOLOCK)有利也有弊,所以在決定使用之前,你一定需要了解清楚WITH(NOLOCK)的功能和缺陷,看其是否適合你的業(yè)務需求,不要覺得它能提升性能,稀里糊涂的就使用它。
1:使用WITH(NOLOCK)時查詢不受其它排他鎖阻塞 打開會話窗口1,執(zhí)行下面腳本,不提交也不回滾事務,模擬事務真在執(zhí)行過程當中 BEGIN TRAN UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1; --ROLLBACK
打開會話窗口2,執(zhí)行下面腳本,你會發(fā)現(xiàn)執(zhí)行結果一直查詢不出來(其實才兩條記錄)。當前會話被阻塞了 SELECT * FROM TEST; 打開會話窗口3,執(zhí)行下面腳本,查看阻塞情況,你會發(fā)現(xiàn)在會話2被會話1給阻塞了,會話2的等待類型為LCK_M_S:“當某任務正在等待獲取共享鎖時出現(xiàn)” SELECT wt.blocking_session_id AS BlockingSessesionId ,sp.program_name AS ProgramName ,COALESCE(sp.LOGINAME, sp.nt_username) AS HostName ,ec1.client_net_address AS ClientIpAddress ,db.name AS DatabaseName ,wt.wait_type AS WaitType ,ec1.connect_time AS BlockingStartTime ,wt.WAIT_DURATION_MS/1000 AS WaitDuration ,ec1.session_id AS BlockedSessionId ,h1.TEXT AS BlockedSQLText ,h2.TEXT AS BlockingSQLText FROM sys.dm_tran_locks AS tl INNER JOIN sys.databases db ON db.database_id = tl.resource_database_id INNER JOIN sys.dm_os_waiting_tasks AS wt ON tl.lock_owner_address = wt.resource_address INNER JOIN sys.dm_exec_connections ec1 ON ec1.session_id = tl.request_session_id INNER JOIN sys.dm_exec_connections ec2 ON ec2.session_id = wt.blocking_session_id LEFT OUTER JOIN master.dbo.sysprocesses sp ON SP.spid = wt.blocking_session_id CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1 CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2
此時查看會話1(會話1的會話ID為53,執(zhí)行腳本1前,可以用SELECT @@spid查看會話ID)的鎖信息情況,你會發(fā)現(xiàn)表TEST(ObjId=1893581784)持有的鎖信息如下所示
打開會話窗口4,執(zhí)行下面腳本.你會發(fā)現(xiàn)查詢結果很快就出來,會話4并不會被會話1阻塞。 SELECT * FROM TEST WITH(NOLOCK) 從上面模擬的這個小例子可以看出,正是由于加上WITH(NOLOCK)提示后,會話1中事務設置的排他鎖不會阻礙當前事務讀取鎖定數(shù)據(jù),所以會話4不會被阻塞,從而提升并發(fā)時查詢性能。
2:WITH(NOLOCK) 不發(fā)布共享鎖來阻止其他事務修改當前事務讀取的數(shù)據(jù),這個就不舉例子了。 本質(zhì)上WITH(NOLOCK)是通過減少鎖和不受排它鎖影響來減少阻塞,從而提高并發(fā)時的性能。所謂凡事有利也有弊,WITH(NOLOCK)在提升性能的同時,也會產(chǎn)生臟讀現(xiàn)象。 如下所示,表TEST有兩條記錄,我準備更新OBJECT_ID=1的記錄,此時事務既沒有提交也沒有回滾 BEGIN TRAN UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1; --ROLLBACK 此時另外一個會話使用WITH(NOLOCK)查到的記錄為未提交的記錄值 假如由于某種原因,該事務回滾了,那么我們讀取到的OBJECT_ID=1的記錄就是一條臟數(shù)據(jù)。 臟讀又稱無效數(shù)據(jù)的讀出,是指在數(shù)據(jù)庫訪問中,事務T1將某一值修改,然后事務T2讀取該值,此后T1因為某種原因撤銷對該值的修改,這就導致了T2所讀取到的數(shù)據(jù)是無效的。
WITH(NOLOCK)使用場景
什么時候可以使用WITH(NOLOCK)? 什么時候不能使用WITH(NOLOCK),這個要視你系統(tǒng)業(yè)務情況,綜合考慮性能情況與業(yè)務要求來決定是否使用WITH(NOLOCK), 例如涉及到金融或會計成本之類的系統(tǒng),出現(xiàn)臟讀那是要產(chǎn)生嚴重問題的。關鍵業(yè)務系統(tǒng)也要慎重考慮。大體來說一般有下面一些場景可以使用WITH(NOLOCK) 1: 基礎數(shù)據(jù)表,這些表的數(shù)據(jù)很少變更。 2:歷史數(shù)據(jù)表,這些表的數(shù)據(jù)很少變更。 3:業(yè)務允許臟讀情況出現(xiàn)涉及的表。 4:數(shù)據(jù)量超大的表,出于性能考慮,而允許臟讀。 另外一點就是不要濫用WITH(NOLOCK),我發(fā)現(xiàn)有個奇怪現(xiàn)象,很多開發(fā)知道WITH(NOLOCK),但是有不了解臟讀,習慣性的使用WITH(NOLOCK)。
WITH(NOLOCK)與 NOLOCK區(qū)別
為了搞清楚WITH(NOLOCK)與NOLOCK的區(qū)別,我查了大量的資料,我們先看看下面三個SQL語句有啥區(qū)別 SELECT * FROM TEST NOLOCK SELECT * FROM TEST (NOLOCK); SELECT * FROM TEST WITH(NOLOCK); 上面的問題概括起來也就是說NOLOCK、(NOLOCK)、 WITH(NOLOCK)的區(qū)別: 1: NOLOCK這樣的寫法,其實NOLOCK其實只是別名的作用,而沒有任何實質(zhì)作用。所以不要粗心將(NOLOCK)寫成NOLOCK 2:(NOLOCK)與WITH(NOLOCK)其實功能上是一樣的。(NOLOCK)只是WITH(NOLOCK)的別名,但是在SQL Server 2008及以后版本中,(NOLOCK)不推薦使用了,"不借助 WITH 關鍵字指定表提示”的寫法已經(jīng)過時了。 具體參見MSDN http://msdn.microsoft.com/zh-cn/library/ms143729%28SQL.100%29.aspx 2.1 至于網(wǎng)上說WITH(NOLOCK)在SQL SERVER 2000不生效,我驗證后發(fā)現(xiàn)完全是個謬論。 2.2 在使用鏈接服務器的SQL當中,(NOLOCK)不會生效,WITH(NOLOCK)才會生效。如下所示 消息 4122,級別 16,狀態(tài) 1,第 1 行 Remote table-valued function calls are not allowed.
3.語法上有些許出入,如下所示 這種語法會報錯 SELECT * FROM sys.indexes WITH(NOLOCK) AS i -Msg 156, Level 15, State 1, Line 1 -Incorrect syntax near the keyword 'AS'. 這種語法正常 SELECT * FROM sys.indexes (NOLOCK) AS i 可以全部改寫為下面語法 SELECT * FROM sys.indexes i WITH(NOLOCK) SELECT * FROM sys.indexes i (NOLOCK)
WITH(NOLOCK)會不會產(chǎn)生鎖 很多人誤以為使用了WITH(NOLOCK)后,數(shù)據(jù)庫庫不會產(chǎn)生任何鎖。實質(zhì)上,使用了WITH(NOLOCK)后,數(shù)據(jù)庫依然對該表對象生成Sch-S(架構穩(wěn)定性)鎖以及DB類型的共享鎖, 如下所示,可以在一個會話中查詢一個大表,然后在另外一個會話中查看鎖信息(也可以使用SQL Profile查看會話鎖信息) 不使用WTIH(NOLOCK) 使用WITH(NOLOCK) 從上可以看出使用WITH(NOLOCK)后,數(shù)據(jù)庫并不是不生成相關鎖。 對比可以發(fā)現(xiàn)使用WITH(NOLOCK)后,數(shù)據(jù)庫只會生成DB類型的共享鎖、以及TAB類型的架構穩(wěn)定性鎖. 另外,使用WITH(NOLOCK)并不是說就不會被其它會話阻塞,依然可能會產(chǎn)生Schema Change Blocking 會話1:執(zhí)行下面SQL語句,暫時不提交,模擬事務正在執(zhí)行 BEGIN TRAN ALTER TABLE TEST ADD Grade VARCHAR(10) ; 會話2:執(zhí)行下面語句,你會發(fā)現(xiàn)會話被阻塞,截圖如下所示。 SELECT * FROM TEST WITH(NOLOCK) |
|