筆記46-徐 一個常見的select ,update ,insert ,delete動作要申請的鎖


筆記46-徐 一個常見的select ,update ,insert ,delete動作要申請的鎖

  1 --一個常見的select ,update ,insert ,delete動作要申請的鎖
  2 
  3 --兩個表都加了索引的字段
  4 --employeeID
  5 --managerid
  6 --modifieddate
  7 
  8 --------------------一個常見的select動作要申請的鎖-----------------------------------------------
  9 --在可重復讀的級別下,共享鎖要保留到事務提交的時候才釋放,所以如果
 10 --這個隔離級別下開啟一個事務,再運行一個查詢語句,就能看到這個查詢所申請的主要
 11 --共享鎖。因此可以使用這種簡單方法分析一下一個查詢語句會申請哪些鎖,並且不需要
 12 --SQL Trace
 13 --(1)在連接A中,將事務隔離級別設置為可重復讀(repeatable read)
 14 --(2)在運行查詢語句之前先開啟一個事務
 15 --(3)運行查詢語句,但是不提交這個事務
 16 --(4)在第二個連接里,查詢sys.dm_tran_locks動態管理視圖來分析查詢結束后連接
 17 --A還持有的鎖
 18 
 19 
 20 --我們先在有聚集索引的那張表上運行一句最簡單的查詢 在SSMS里新建一個查詢---------
 21 --查詢一:
 22 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 23 GO
 24 SET STATISTICS PROFILE ON
 25 GO
 26 --以下查詢使用了聚集索引查找 ctrl+l
 27 BEGIN TRAN
 28 SELECT [EmployeeID],[LoginID],[Title] FROM [dbo].[Employee_Demo_BTree] WHERE [EmployeeID] IN(3,30,200)
 29 -------------------------------------------------------------------------------------------------------
 30 
 31 --查看DMV看一下有多少個鎖被這個連接持有-----------------------------------------------------------
 32 USE [AdventureWorks] --要查詢申請鎖的數據庫
 33 GO
 34 SELECT
 35 [request_session_id],
 36 c.[program_name],
 37 DB_NAME(c.[dbid]) AS dbname,
 38 [resource_type],
 39 [request_status],
 40 [request_mode],
 41 [resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
 42 p.[index_id]
 43 FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
 44 ON a.[resource_associated_entity_id]=p.[hobt_id]
 45 LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
 46 WHERE c.[dbid]=DB_ID('AdventureWorks')  ----要查詢申請鎖的數據庫
 47 ORDER BY [request_session_id],[resource_type]
 48 
 49 
 50 --查詢一所持有的鎖
 51 --(1)因為連接正在訪問數據庫[AdventureWorks],所以在數據庫一級加了一個共享鎖,以防止
 52 --別人將數據庫刪除
 53 
 54 
 55 --(2)因為正在訪問表格[Employee_Demo_BTree],所以在表格上加了一個意向共享鎖,以防止
 56 --別人修改表的定義  resource_type:object
 57 
 58 
 59 --(3)查詢有3條記錄返回,所以在這3條記錄所在的聚集索引鍵上,分別持有一個共享鎖。
 60 --在這3個鍵所在的頁面上,持有一個意向共享鎖  resource_type:page,key 鎖住整頁
 61 
 62 --可以說,這個查詢申請鎖的數目是很少的。其他用戶訪問同一張表,只要不訪問這3條記錄
 63 --,就不會影響到。這是因為查詢使用了“聚集索引查找”的關系
 64 
 65 ---------------------------------------------------------------------------------------------
 66 
 67 --運行查詢二:在SSMS里新建一個查詢,運行之前記得將前面查詢一的事務提交或者回滾 多次運行最后那句COMMIT TRAN
 68 --BEGIN TRAN
 69 --SELECT [EmployeeID],[LoginID],[Title] FROM [dbo].[Employee_Demo_BTree] WHERE [EmployeeID] IN(3,30,200)
 70 --COMMIT TRAN
 71 
 72 --查詢二
 73 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 74 GO
 75 SET STATISTICS PROFILE ON
 76 GO
 77 BEGIN TRAN
 78 SELECT
 79 [EmployeeID],
 80 [LoginID],
 81 [Title]
 82 FROM [dbo].[Employee_Demo_Heap]
 83 WHERE [EmployeeID]=3
 84 
 85 --COMMIT TRAN
 86 --運行之后最后記得加上COMMIT TRAN
 87 
 88 ---------------分析-------------------------------------------
 89 --因為[Employee_Demo_Heap]的[EmployeeID]上是一個非聚集索引,所以SQL在用非聚集索引
 90 --找到這條記錄之后,必須再到數據頁面上把其他的行上面的數據找出來(所謂的“書簽查找” bookmark lookup)
 91 --從申請的鎖上也能看出來,雖然只返回一條記錄,可是他在[PK_Employee_EmployeeID_Demo_Heap]
 92 --(index_id是2 表明是非聚集索引)上申請了一個key鎖,在RID(datapage數據頁上的行row)
 93 --申請了一個row鎖。在這兩個資源所在的頁面上各申請了一個page意向鎖
 94 
 95 
 96 --與上面的例子比較,雖然查詢二返回的結果和查詢一是一樣的,但是由於他使用的是非聚集索引+書簽查找
 97 --bookmark lookup,所以申請的鎖的數目要比查詢一多。一個查詢要使用的索引鍵(或者RID)數目越多
 98 --,他申請的鎖也就會越多。沒有用到的索引上不會申請共享鎖
 99 
100 
101 
102 
103 --那么是不是所有的查詢都只在返回的記錄上加鎖呢?現在再來做下面這個試驗,運行他之前
104 --請記得將前面那個事務提交或回滾
105 
106 --------------------------------------------------------------------------------------
107 --修改一 :update語句:在SSMS里新建一個查詢
108 --首先開啟一個事務,修改一條查詢不會返回記錄
109 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
110 GO
111 SET STATISTICS PROFILE ON
112 GO
113 BEGIN TRAN
114 UPDATE [dbo].[Employee_Demo_Heap]
115 SET [Title]='aaa'
116 WHERE [EmployeeID]=70
117 
118 --再在另一個連接里運行查詢三
119 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
120 GO
121 SET STATISTICS PROFILE ON
122 GO
123 BEGIN TRAN
124 SELECT
125 [EmployeeID],
126 [LoginID],
127 [Title]
128 FROM [dbo].[Employee_Demo_Heap]
129 WHERE [EmployeeID] IN(3,30,200)
130 
131 
132 ---------------------分析--------------------------------------------------
133 --由於要返回3條分布在不同數據頁上的記錄,SQL認為做非聚集索引+書簽查找並不比
134 --做一個表掃描快,所以他直接選擇了一個表掃描。
135 
136 --DMV顯示查詢三已經得到了RID 1:3181:2和1:3181:29上的鎖,他們應該是EmployeeID為3和30
137 --的,他在往下找EmployeeID為200的時候,讀到了RID 1:3208:22。他是EmployeeID為70,
138 --被上面的update語句修改了,update那個事務還沒有提交,所以查詢三被阻塞了
139 
140 --現在把修改一回滾,阻塞解除,查詢三能夠執行完畢,可以看到他的執行計划與他持有的鎖
141 
142 --SQL一直往下找
143 --書簽
144 --70   ->還沒有提交
145 --80
146 --90
147 --100
148 --.
149 --.
150 --.
151 --.
152 --.
153 --.
154 --200
155 
156 --所以不能繼續往下找,如果跳過的話,他不知道跳過的是不是就是他要找的那條記錄,所以阻塞了
157 --一個RID對應一個EmployeeID
158 
159 --與查詢一不同的是,查詢三不但在這三條記錄所在頁面(1:3211,1:3181)申請了IS鎖,
160 --還在表格的所有頁面上都申請了IS鎖。所以這就是全表掃描,掃描了所有的數據頁面帶來
161 --的后果。更嚴重的是,查詢三在掃描每一張頁面的時候,會對讀到的每一個數據記錄加上
162 --一個共享鎖(讀完了這條記錄就會釋放,不用等到整個語句結束)只要有任何一個記錄
163 --上的鎖沒有申請到,查詢就會被阻塞
164 
165 
166 --------------------------------------------------------------------------------------------
167 --運行修改二,再運行查詢一
168 --修改二:update語句:在SSMS里新建一個查詢
169 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
170 GO
171 SET STATISTICS PROFILE ON
172 GO
173 BEGIN TRAN
174 UPDATE [dbo].[Employee_Demo_BTree]
175 SET [Title]='aaa'
176 WHERE [EmployeeID]=70
177 
178 
179 
180 --再在另一個連接里運行查詢一
181 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
182 GO
183 SET STATISTICS PROFILE ON
184 GO
185 --以下查詢使用了聚集索引查找 ctrl+l
186 BEGIN TRAN
187 SELECT [EmployeeID],[LoginID],[Title] FROM [dbo].[Employee_Demo_BTree] WHERE [EmployeeID] IN(3,30,200)
188 
189 
190 ---------------------------分析-------------------------------------------------------------
191 --這是因為查詢一使用的是索引查找index seek,不需要每條記錄都讀一遍,所以就不用
192 --去讀EmployeeID70,也就不會被阻塞住  因為select是select書簽里面的內容根本不用到表里去讀取
193 --數據
194 
195 
196 
197 --------------------------總結select動作--------------------------------------------------------
198 --規律:在非“未提交讀”的隔離級別上
199 --已提交讀
200 --可重復讀
201 --可序列化
202 --(1)查詢在運行過程中,會對每一條讀到的記錄或鍵值加共享鎖。如果記錄不用返回。
203 --那鎖就會被釋放。如果記錄需要被返回,則視隔離級別而定,如果是“已提交讀”,則也釋放
204 --否則,不釋放
205 
206 --(2)對每一個使用到的索引,SQL也會對上面的鍵值加共享鎖
207 
208 --(3)對每個讀過的頁面,SQL會加一個意向鎖
209 
210 --(4)查詢需要掃描頁面和記錄越多,鎖的數目也會越多。查詢用到的索引越多,鎖的數目
211 --也會越多
212 
213 
214 --避免阻塞采取的方法
215 --(1)盡量返回少的記錄集,返回的結果越多,需要的鎖也就越多
216 
217 --(2)如果返回結果集只是表格所有記錄的一小部分,要盡量使用index seek,避免全表掃描這種
218 --執行計划
219 
220 --(3)可能的話,設計好合適的索引,避免SQL通過多個索引才找到數據
221 
222 --當然,這些對於“已提交讀”以上隔離級別而言。如果使用“未提交讀”,SQL就不會申請這些共享鎖
223 --阻塞也不會發生
224 
225 
226 
227 
228 --------------------一個常見的update動作要申請的鎖-----------------------------------------------
229 --對於update語句,可以簡單理解為SQL先做查詢,把需要修改的記錄給找到,然后在這個記錄
230 --上做修改。找記錄的動作要加S鎖,找到修改的記錄后加U鎖,再將U鎖升級為X鎖。
231 
232 --這里用上面兩張表做例子,選用repeatable read的隔離級別,運行一個update語句
233 USE [AdventureWorks]
234 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
235 GO
236 BEGIN TRAN
237 UPDATE [dbo].[Employee_Demo_Heap]
238 SET [Title]='changeheap'
239 WHERE [EmployeeID] IN(3,30,200)
240 
241 
242 --這個update語句在非聚集索引上申請了3個U鎖,在RID上申請了3個X鎖。這是因為語句借助非聚集索引
243 --PK_Employee_EmployeeID_Demo_Heap(index_id是2)找到了這3條記錄。非聚集索引PK_Employee_EmployeeID_Demo_Heap
244 --本身沒有用到Title這一列,所以他自己不需要做修改。但是數據RID上有了修改,所以RID上加的是X鎖,其他
245 --索引上沒有加鎖
246 
247 --從這個例子可以看出,如果update借助了哪個索引,這個索引的鍵值上就會有U鎖,沒有用到的
248 --索引上沒有鎖。真正修改發生的地方會有X鎖。對於查詢涉及的頁面,SQL加了IU鎖意向更新鎖,修改
249 --發生的頁面,SQL加了IX鎖 意向排他鎖 (先查詢再修改)鎖key 鎖索引鍵值 因為修改的列沒有被索引
250 
251 --如果修改的列被一個索引使用到了,會是什么情況呢?為了完成這個測試,先在Employee_Demo_BTree
252 --上建一個會被修改的索引
253 
254 CREATE NONCLUSTERED INDEX [Employee_Demo_BTree_Title] ON [AdventureWorks].[dbo].[Employee_Demo_BTree]
255 ([Title] ASC)
256 
257 
258 --再運行下面語句
259 USE [AdventureWorks]
260 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
261 GO
262 BEGIN TRAN
263 UPDATE [dbo].[Employee_Demo_Heap]
264 SET [Title]='changeheap'
265 WHERE [EmployeeID] IN(3,30,200)
266 
267 
268 
269 --語句利用聚集索引找到要修改的3條記錄.但是我們看到有9個鍵上有X鎖。
270 --很有意思:PK_Employee_EmployeeID_Demo_BTree(index_id=1)聚集索引,也是數據存放的地方。
271 --剛才做的update語句沒有改到他的索引列,他只需把Title這個列的值改掉。所以在index1上,
272 --他只申請3個X鎖,每條記錄一個
273 
274 --但是表格在Title上面有一個非聚集索引IX_Employee_ManagerID_Demo_BTree(index_id=5),
275 --並且Title是第一列。他被修改后,原來的索引鍵值就要被刪除掉,並且插入新的鍵值。
276 --所以在index_id=5 上要申請6個X鎖,老的鍵值3個,新的鍵值3個
277 
278 --因為其他索引沒有使用到Title這一列,所以他們上面都沒有申請鎖
279 --這就是9個key鎖的來源
280 
281 
282 --------------------update語句的規律---------------------------------------------------------
283 --(1)對每一個使用到的索引,SQL會對上面的鍵值加U鎖
284 
285 --(2)SQL只對要做修改的記錄或鍵值加X鎖
286 
287 --(3)使用到要修改的列的索引越多,鎖的數目也會越多
288 
289 --(4)掃描過的頁面越多,意向鎖也會越多。在掃描的過程中,對所有掃描到的記錄也會加鎖,哪怕
290 --上面沒有修改
291 
292 -----------------------------結論-------------------------------------------------------
293 --想降低一個update語句被別人阻塞住的幾率,除了注意他的查詢部分之外,數據庫設計者
294 --還要做的事情有:
295 
296 --(1)盡量修改少的記錄集。修改的記錄越多,需要的鎖也就越多
297 
298 --(2)盡量減少無謂的索引。索引的數目越多,需要的鎖也可能越多
299 
300 --(3)但是也要嚴格避免表掃描的發生。如果只是修改表格記錄的一小部分,要盡量使用index seek索引查找
301 --避免全表掃描這種執行計划
302 
303 
304 
305 
306 
307 
308 --------------------一個常見的delete動作要申請的鎖-----------------------------------------------
309 --這次使用read committed這個默認隔離級別
310 
311 USE [AdventureWorks]
312 BEGIN TRAN
313 DELETE [dbo].[Employee_Demo_BTree]
314 WHERE [LoginID]='adventure-works\kim1'
315 
316 
317 --可以看到delete語句在聚集索引(index_id=1)和兩個非聚集索引(index_id=2和3)上各申請了一個X鎖
318 --在她們所在的頁面上申請了一個IX鎖
319 
320 --如果使用repeatable read這個級別運行上面的delete命令,就能看出好像做select的時候一樣,做
321 --delete的時候SQL也需要先找到要刪除的記錄。在找的過程中也會加鎖
322 
323 --現在運行一個新的delete語句,會使用全表掃描
324 
325 USE [AdventureWorks]
326 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
327 GO
328 BEGIN TRAN
329 DELETE [dbo].[Employee_Demo_Heap]
330 WHERE [LoginID]='adventure-works\tete0'
331 
332 --可以看到delete語句在3個非聚集索引(index_id=2、3、4)上各申請了一個X鎖。在她們
333 --所在的頁面上申請了一個IX鎖。在修改發生的heap數據頁面上,申請了一個IX鎖,相應的
334 --RID上(真正的數據記錄)申請了一個X鎖。其他掃描過的頁面申請了IU鎖
335 
336 ----------------------------規律-----------------------------------------------------------------
337 --(1)delete的過程是先找到符號條件的記錄,然后做刪除。可以理解為先是一個select,然后
338 --是delete.所以,如果有合適的索引,第一步申請的鎖就會比較少 不用表掃描
339 
340 --(2)delete不但是把數據行本身刪除,還要刪除所有相關的索引鍵.所以一張表上索引數目越多
341 --鎖的數目就會越多,也就越容易發生阻塞
342 
343 --為了防止阻塞,我們既不能絕對地不建索引,也不能隨隨便便地建立很多索引,
344 --而是要建立對查找有利的索引.對於沒有使用到的索引,還是去掉比較好
345 
346 
347 
348 
349 
350 --------------------一個常見的insert動作要申請的鎖-----------------------------------------------
351 --相對於select,update,delete,單條記錄的insert操作對鎖的申請比較簡單。SQL會為新插入
352 --的數據本身申請一個X鎖,在發生變化的頁面上申請一個IX鎖。由於這條記錄是新插入的,被
353 --其他連接引用到的概率會相對小一些,所以出現阻塞的幾率也要小
354 
355 --還是用剛才的那兩張表做例子。首先要插入的是heap結構的表
356 USE [AdventureWorks]
357 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
358 GO
359 BEGIN TRAN
360 INSERT INTO [dbo].[Employee_Demo_Heap]
361         ( [EmployeeID] ,
362           [NationalIDNumber] ,
363           [ContactID] ,
364           [LoginID] ,
365           [ManagerID] ,
366           [Title] ,
367           [BirthDate] ,
368           [MaritalStatus] ,
369           [Gender] ,
370           [HireDate] ,
371           [ModifiedDate]
372         )
373 SELECT
374 501,
375 480168528,
376 1009,
377 'adventure-works\thierry0',
378 263,
379 'Tool Desinger',
380 '1949-08-29 00:00:00.000',
381 'M',
382 'M',
383 '1998-01-11 00:00:00.000',
384 '2004-07-31 00:00:00.000'
385 
386 
387 
388 
389 --如果插入的是有B樹結構的表格
390 
391 USE [AdventureWorks]
392 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
393 GO
394 BEGIN TRAN
395 INSERT INTO [dbo].[Employee_Demo_BTree]
396         ( [EmployeeID] ,
397           [NationalIDNumber] ,
398           [ContactID] ,
399           [LoginID] ,
400           [ManagerID] ,
401           [Title] ,
402           [BirthDate] ,
403           [MaritalStatus] ,
404           [Gender] ,
405           [HireDate] ,
406           [ModifiedDate]
407         )
408 SELECT
409 501,
410 480168528,
411 1009,
412 'adventure-works\thierry0',
413 263,
414 'Tool Desinger',
415 '1949-08-29 00:00:00.000',
416 'M',
417 'M',
418 '1998-01-11 00:00:00.000',
419 '2004-07-31 00:00:00.000'
420 
421 
422 --兩者都申請了以下鎖資源:
423 --(1)數據庫上的S鎖(resource_type=DATABASE)
424 
425 --(2)表上的IX鎖(resource_type=OBJECT)
426 
427 --(3)每個索引上都要插入一條新數據,所以有一個key上的X鎖
428 
429 --(4)在每個索引上發生變化的那個頁面,申請了一個IX鎖(resource_type=PAGE)
430 
431 --唯一不同的是,是在heap結構上還得申請一個RID鎖。因為真正的數據不是放在索引上,而是放在heap數據頁面上
432 
433 
434 
435 
436 
437 
438 ----------------------------------------結論-------------------------------------------
439 --如果SQLDBA要控制SQL鎖的申請和釋放行為,以緩解阻塞和死鎖問題,需要考慮的因素有:
440 --1、事務隔離級別的選定
441 --事務隔離級別越高,隔離度就越高,並發度也就越差。如果選擇了比較高的隔離級別,SQL
442 --不可避免地要申請更多的鎖,持有的時間也會增加。所以在設計應用的時候,一定要
443 --和用戶談好,盡量選擇默認的隔離級別(read committed)
444 
445 
446 --2、事務的長短和事務的復雜度
447 --事務的長度和復雜度決定論這個事務在SQL內部會持續多長時間,也能決定SQL會同時在
448 --多少張表和索引上申請和持有鎖。事務越簡單,就越不容易發生阻塞和死鎖。所以這
449 --也必須和用戶商量好,盡量避免在一個事務里做很多事情
450 
451 
452 --3、從應用整體並發度考慮,單個事務一次處理的數據量不能過多
453 --應用的性能,不單要衡量單個連接的處理速度,也要衡量在並發處理的情況下,整體
454 --的平均速度怎么樣。從連接個體來講,可能在一個事務里把數據一次都處理掉比較快
455 --但是如果處理的數據量很大,就會影響到其他連接同時訪問同一對象。所以,如果
456 --一個應用的並發要求比較高,就一定要嚴格控制單個事務處理的數據量。如果有什么
457 --事務操作需要訪問或修改表格內的大量數據,最好調整到並發用戶比較少的時候運行
458 
459 
460 --4、針對語句在表格上設計合適的索引
461 --合適的索引能使SQL在讀取盡可能少的數據量的前提下,把需要處理的數據找到。如果
462 --沒有合適的索引,SQL在做select,update,delete的時候,會申請比要處理的目標數據量
463 --多得多的鎖,從而導致阻塞或死鎖。這種情形可以通過加索引的方式提高並發度
464 --同時,SQL在做update,insert,delete的時候,會對有關聯的所有索引都做修改,在她們
465 --上面申請鎖。從這個角度講,索引越多,產生的鎖的數目也就越多,阻塞和死鎖的幾率
466 --也就會越高
467 
468 --所以數據庫設計員需要做的,是要確保有足夠的索引,防止語句做全表掃描,但是也要
469 --去掉那些對語句運行貢獻不大的索引。不能隨便往表格上加索引

 


免責聲明!

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



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