การหยุดชะงักเมื่ออัพเดตแถวต่าง ๆ ด้วยดัชนีที่ไม่ทำคลัสเตอร์


13

ฉันกำลังแก้ไขปัญหาการหยุดชะงักในขณะที่ฉันสังเกตเห็นว่าพฤติกรรมการล็อคแตกต่างกันเมื่อฉันใช้ดัชนีแบบคลัสเตอร์และแบบไม่รวมกลุ่มบนฟิลด์รหัส ปัญหาการหยุดชะงักดูเหมือนว่าจะได้รับการแก้ไขหากดัชนีหรือคีย์หลักที่ถูก clusted ถูกนำไปใช้กับฟิลด์ id

ฉันมีการทำธุรกรรมที่แตกต่างกันที่ทำการอัปเดตแถวที่แตกต่างกันอย่างน้อยหนึ่งรายการเช่นธุรกรรม A จะอัปเดตแถวที่มี ID = a เท่านั้น tx B จะแตะแถวที่มี ID = b เท่านั้น

และฉันเข้าใจดีว่าหากไม่มีดัชนีการอัปเดตจะได้รับการอัพเดตการล็อคสำหรับแถวทั้งหมดและแปลงเป็นล็อคแบบเอกสิทธิ์เฉพาะบุคคลเมื่อจำเป็นซึ่งในที่สุดจะนำไปสู่การหยุดชะงัก แต่ฉันล้มเหลวในการค้นหาสาเหตุที่มีดัชนีที่ไม่ใช่คลัสเตอร์การหยุดชะงักยังคงอยู่ที่นั่น (แม้ว่าอัตราการเข้าชมดูเหมือนจะลดลง)

ตารางข้อมูล:

CREATE TABLE [dbo].[user](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [userName] [nvarchar](255) NULL,
    [name] [nvarchar](255) NULL,
    [phone] [nvarchar](255) NULL,
    [password] [nvarchar](255) NULL,
    [ip] [nvarchar](30) NULL,
    [email] [nvarchar](255) NULL,
    [pubDate] [datetime] NULL,
    [todoOrder] [text] NULL
)

ร่องรอยการหยุดชะงัก

deadlock-list
deadlock victim=process4152ca8
process-list
process id=process4152ca8 taskpriority=0 logused=0 waitresource=RID: 5:1:388:29 waittime=3308 ownerId=252354 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.947 XDES=0xb0bf180 lockMode=U schedulerid=3 kpid=11392 status=suspended spid=57 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.953 lastbatchcompleted=2014-04-11T00:15:30.950 lastattention=1900-01-01T00:00:00.950 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252354 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=62 sqlhandle=0x0200000062f45209ccf17a0e76c2389eb409d7d970b0f89e00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(2)<c/>@owner int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
process id=process4153468 taskpriority=0 logused=4652 waitresource=KEY: 5:72057594042187776 (3fc56173665b) waittime=3303 ownerId=252344 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.920 XDES=0x4184b78 lockMode=U schedulerid=3 kpid=7272 status=suspended spid=58 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.960 lastbatchcompleted=2014-04-11T00:15:30.960 lastattention=1900-01-01T00:00:00.960 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252344 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=60 sqlhandle=0x02000000d4616f250747930a4cd34716b610a8113cb92fbc00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(61)<c/>@uid int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
resource-list
ridlock fileid=1 pageid=388 dbid=5 objectname=SQL2012_707688_webows.dbo.user id=lock3f7af780 mode=X associatedObjectId=72057594042122240
owner-list
owner id=process4153468 mode=X
waiter-list
waiter id=process4152ca8 mode=U requestType=wait
keylock hobtid=72057594042187776 dbid=5 objectname=SQL2012_707688_webows.dbo.user indexname=10 id=lock3f7ad700 mode=U associatedObjectId=72057594042187776
owner-list
owner id=process4152ca8 mode=U
waiter-list
waiter id=process4153468 mode=U requestType=wait

สิ่งที่น่าสนใจและเป็นไปได้ที่เกี่ยวข้องก็คือดัชนีที่จัดกลุ่มและไม่รวมกลุ่มดูเหมือนว่าจะมีพฤติกรรมการล็อคที่แตกต่างกัน

เมื่อใช้ดัชนีคลัสเตอร์จะมีการล็อคแบบเอกสิทธิ์เฉพาะบุคคลบนคีย์เช่นเดียวกับการล็อคแบบเอกสิทธิ์เฉพาะบุคคลใน RID เมื่อทำการปรับปรุงซึ่งคาดว่า; ในขณะที่มีการล็อกแบบเอกสิทธิ์เฉพาะบุคคลที่สองใน RID ที่แตกต่างกันสองถ้าใช้ดัชนีที่ไม่ใช่คลัสเตอร์ซึ่งทำให้ฉันสับสน

จะเป็นประโยชน์ถ้าใครสามารถอธิบายว่าทำไมถึงเป็นเช่นนี้

ทดสอบ SQL:

use SQL2012_707688_webows;
begin transaction;
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
exec sp_lock;
commit;

ด้วย id เป็นดัชนีแบบคลัสเตอร์:

spid    dbid    ObjId   IndId   Type    Resource    Mode    Status
53  5   917578307   1   KEY (b1a92fe5eed4)                      X   GRANT
53  5   917578307   1   PAG 1:879                               IX  GRANT
53  5   917578307   1   PAG 1:1928                              IX  GRANT
53  5   917578307   1   RID 1:879:7                             X   GRANT

ด้วย id เป็นดัชนีที่ไม่คลัสเตอร์

spid    dbid    ObjId   IndId   Type    Resource    Mode    Status
53  5   917578307   0   PAG 1:879                               IX  GRANT
53  5   917578307   0   PAG 1:1928                              IX  GRANT
53  5   917578307   0   RID 1:879:7                             X   GRANT
53  5   917578307   0   RID 1:1928:18                           X   GRANT

EDIT1: รายละเอียดของการหยุดชะงักโดยไม่มีดัชนีใด ๆ
สมมติว่าฉันมีสอง tx A และ B แต่ละคนมีสองคำสั่งการปรับปรุงสองแถวที่แตกต่างกันของหลักสูตร
tx A

update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501

tx B

update [user] with (rowlock) set todoOrder='{3}' where id = 63502
update [user] with (rowlock) set todoOrder='{4}' where id = 63502

{1} และ {4} จะมีโอกาสเกิดการหยุดชะงักเนื่องจาก

ที่ {1}, การล็อก U ถูกร้องขอสำหรับแถว 63502 เนื่องจากต้องทำการสแกนตารางและ X lock อาจถูกยึดในแถว 63501 เนื่องจากตรงกับเงื่อนไข

ที่ {4}, การล็อก U ถูกร้องขอสำหรับแถว 63501, และการล็อก X เก็บไว้ที่ 63502 แล้ว

ดังนั้นเรามี txA ถือ 63501 และรอ 63502 ในขณะที่ txB ถือ 63502 กำลังรอ 63501 ซึ่งเป็นการหยุดชะงัก

แก้ไข 2: เปิดออกข้อบกพร่องของกรณีทดสอบของฉันทำให้สถานการณ์แตกต่างกันที่นี่ ขออภัยในความสับสน แต่ข้อผิดพลาดทำให้สถานการณ์แตกต่างกันและดูเหมือนว่าจะทำให้เกิดการหยุดชะงักในที่สุด

เนื่องจากการวิเคราะห์ของ Paul ช่วยฉันได้อย่างแท้จริงในกรณีนี้ดังนั้นฉันจึงยอมรับว่าเป็นคำตอบ

เนื่องจากข้อผิดพลาดของกรณีทดสอบของฉันธุรกรรมสองรายการ txA และ txB สามารถอัปเดตแถวเดียวกันดังต่อไปนี้:

tx A

update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501

tx B

update [user] with (rowlock) set todoOrder='{3}' where id = 63501

{2} และ {3} จะมีโอกาสเกิดการหยุดชะงักเมื่อ:

txA ร้องขอ U lock บนคีย์ขณะถือ X lock บน RID (เนื่องจากอัพเดต {1}) txB ร้องขอ U lock บน RID ในขณะที่กด U lock บนคีย์


1
ฉันไม่สามารถระบุได้ว่าเหตุใดธุรกรรมจึงต้องอัปเดตแถวเดียวกันสองครั้ง
ypercubeᵀᴹ

@percube จุดที่ดีนั่นคือสิ่งที่ฉันควรปรับปรุง แต่ในกรณีนี้ฉันเพียงต้องการที่จะเข้าใจพฤติกรรมการล็อคได้ดีขึ้น
Bood

@ypercube หลังจากคิดเพิ่มเติมฉันคิดว่าเป็นไปได้ว่าแอปพลิเคชันที่มีตรรกะที่ซับซ้อนจำเป็นต้องอัปเดตแถวเดียวกันสองครั้งใน tx เดียวกันอาจเป็นคอลัมน์ที่แตกต่างกันได้เช่นกัน
Bood

คำตอบ:


16

... ทำไมกับดัชนีคลัสเตอร์การหยุดชะงักยังคงอยู่ที่นั่น (แม้ว่าอัตราการเข้าชมดูเหมือนว่าจะลดลง)

คำถามนั้นไม่ชัดเจน (เช่นจำนวนอัพเดตและidค่าที่อยู่ในแต่ละธุรกรรม) แต่สถานการณ์เดดล็อกที่ชัดเจนหนึ่งเกิดขึ้นกับการอัพเดตแถวเดี่ยวหลายรายการภายในธุรกรรมเดียวที่มีการทับซ้อนของ[id]ค่าและรหัสเป็น อัปเดต[id]ตามลำดับอื่น:

[T1]: Update id 2; Update id 1;
[T2]: Update id 1; Update id 2;

ลำดับการหยุดชะงัก: T1 (U2), T2 (U1) T1 (U1) รอ , T2 (U2) รอ

ลำดับการหยุดชะงักนี้อาจหลีกเลี่ยงได้โดยการอัปเดตอย่างเข้มงวดตามคำสั่ง id ภายในแต่ละธุรกรรม (การรับการล็อกในลำดับเดียวกันบนเส้นทางเดียวกัน)

เมื่อใช้ดัชนีคลัสเตอร์จะมีการล็อคแบบเอกสิทธิ์เฉพาะบุคคลบนคีย์เช่นเดียวกับการล็อคแบบเอกสิทธิ์เฉพาะบุคคลใน RID เมื่อทำการปรับปรุงซึ่งคาดว่า; ในขณะที่มีการล็อกแบบเอกสิทธิ์เฉพาะบุคคลที่สองใน RID ที่แตกต่างกันสองถ้าใช้ดัชนีที่ไม่ใช่คลัสเตอร์ซึ่งทำให้ฉันสับสน

เมื่อเปิดดัชนีคลัสเตอร์ที่ไม่ซ้ำกันจะidมีการล็อกแบบเอกสิทธิ์เฉพาะบุคคลบนคีย์การทำคลัสเตอร์เพื่อป้องกันการเขียนข้อมูลในแถว RIDต้องใช้การล็อกแบบเอกสิทธิ์เฉพาะบุคคลแยกต่างหากเพื่อป้องกันการเขียนลงในtextคอลัมน์LOB ซึ่งถูกเก็บไว้ในหน้าข้อมูลแยกต่างหากโดยค่าเริ่มต้น

เมื่อตารางเป็นฮีปที่มีดัชนีที่ไม่ใช่คลัสเตอร์เท่านั้นidสิ่งสองสิ่งจะเกิดขึ้น ก่อนอื่นRIDล็อคพิเศษหนึ่งอันเกี่ยวข้องกับข้อมูลฮีปในแถวและอีกอันหนึ่งคือล็อคข้อมูล LOB เหมือนเมื่อก่อน ผลกระทบที่สองคือต้องมีแผนการดำเนินการที่ซับซ้อนมากขึ้น

ด้วยดัชนีคลัสเตอร์และการอัพเดตเพรดิเคตค่าความเท่าเทียมแบบง่ายค่าเดียวตัวประมวลผลเคียวรีสามารถใช้การปรับให้เหมาะสมที่ดำเนินการอัพเดต (อ่านและเขียน) ในตัวดำเนินการเดี่ยวโดยใช้พา ธ เดียว:

อัปเดตผู้ดำเนินการเดี่ยว

แถวนั้นอยู่ในตำแหน่งและได้รับการอัพเดตในการค้นหาครั้งเดียวซึ่งต้องการการล็อคแบบเอกสิทธิ์เฉพาะบุคคลเท่านั้น (ไม่จำเป็นต้องล็อคการอัปเดต) ตัวอย่างการล็อกตัวอย่างโดยใช้ตารางตัวอย่างของคุณ:

acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IX lock on PAGE: 6:1:59104 -- INROW
acquiring X lock on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
acquiring IX lock on PAGE: 6:1:59091 -- LOB
acquiring X lock on RID: 6:1:59091:1 -- LOB

releasing lock reference on PAGE: 6:1:59091 -- LOB
releasing lock reference on RID: 6:1:59091:1 -- LOB
releasing lock reference on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
releasing lock reference on PAGE: 6:1:59104 -- INROW

ด้วยดัชนีที่ไม่เป็นคลัสเตอร์เท่านั้นไม่สามารถใช้การปรับให้เหมาะสมแบบเดียวกันได้เนื่องจากเราจำเป็นต้องอ่านจากโครงสร้าง b-tree หนึ่งและเขียนโครงสร้างอื่น แผนหลายเส้นทางมีเฟสการอ่านและเขียนแยกกัน:

การอัพเดตหลายตัววนซ้ำ

สิ่งนี้จะได้รับการล็อคการอัพเดทเมื่ออ่านโดยแปลงเป็นล็อคแบบเอกสิทธิ์เฉพาะบุคคลหากแถวนั้นมีคุณสมบัติเหมาะสม ตัวอย่างล็อคลำดับที่มีคีมาให้:

acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IU lock on PAGE: 6:1:59105 -- NC INDEX
acquiring U lock on KEY: 6:72057594233749504 (61a06abd401c) -- NC INDEX
acquiring IU lock on PAGE: 6:1:59104 -- HEAP
acquiring U lock on RID: 6:1:59104:1 -- HEAP
acquiring IX lock on PAGE: 6:1:59104 -- HEAP convert to X
acquiring X lock on RID: 6:1:59104:1 -- HEAP convert to X
acquiring IU lock on PAGE: 6:1:59091 -- LOB
acquiring U lock on RID: 6:1:59091:1 -- LOB

releasing lock reference on PAGE: 6:1:59091 
releasing lock reference on RID: 6:1:59091:1
releasing lock reference on RID: 6:1:59104:1
releasing lock reference on PAGE: 6:1:59104 
releasing lock on KEY: 6:72057594233749504 (61a06abd401c)
releasing lock on PAGE: 6:1:59105 

หมายเหตุข้อมูล LOB ถูกอ่านและเขียนที่ตัวอัพเดตตารางการอัพเดต แผนที่ซับซ้อนมากขึ้นและเส้นทางการอ่านและเขียนหลายเส้นทางจะเพิ่มโอกาสในการหยุดชะงัก

ในที่สุดฉันก็อดไม่ได้ที่จะสังเกตเห็นชนิดข้อมูลที่ใช้ในคำจำกัดความของตาราง คุณไม่ควรใช้textชนิดข้อมูลที่เลิกใช้สำหรับงานใหม่ ทางเลือกถ้าคุณต้องการความสามารถในการเก็บได้ถึง 2GB varchar(max)ของข้อมูลในคอลัมน์นี้จริงๆเป็น ความแตกต่างที่สำคัญอย่างหนึ่งระหว่างtextและvarchar(max)คือtextข้อมูลจะถูกจัดเก็บแบบ off-row โดยค่าเริ่มต้นในขณะที่จัดvarchar(max)เก็บแบบ in-row

ใช้ประเภท Unicode ก็ต่อเมื่อคุณต้องการความยืดหยุ่นนั้น (เช่นยากที่จะดูว่าทำไมที่อยู่ IP จึงต้องใช้ Unicode) นอกจากนี้ให้เลือกการจำกัดความยาวที่เหมาะสมสำหรับแอตทริบิวต์ของคุณ - 255 ทุกที่ดูเหมือนไม่ถูกต้อง

อ่านเพิ่มเติม:
Deadlock และรูปแบบทั่วไป
ของชุดแก้ไขปัญหาการหยุดชะงักของ Bart Duncan

การติดตามการล็อกสามารถทำได้หลายวิธี SQL Server Express พร้อมบริการขั้นสูง ( 2014 และ 2012 SP1 เป็นต้นไป ) มีเครื่องมือProfilerซึ่งเป็นวิธีที่ได้รับการสนับสนุนเพื่อดูรายละเอียดของการได้มาและการปลดล็อค


คำตอบที่ยอดเยี่ยม คุณจะส่งออกบันทึก / ติดตามที่มีข้อความ "กำลังได้รับ ... ล็อค" และ "ปล่อยการอ้างอิงล็อค" อย่างไร
Sanjiv Jivan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.