INSERTs ขนาดใหญ่ปิดกั้นการเลือก


14

ฉันมีปัญหากับ INSERT จำนวนมากที่บล็อกการดำเนินการ SELECT ของฉัน

schema

ฉันมีโต๊ะแบบนี้:

CREATE TABLE [InverterData](
    [InverterID] [bigint] NOT NULL,
    [TimeStamp] [datetime] NOT NULL,    
    [ValueA] [decimal](18, 2) NULL,
    [ValueB] [decimal](18, 2) NULL
    CONSTRAINT [PrimaryKey_e149e28f-5754-4229-be01-65fafeebce16] PRIMARY KEY CLUSTERED 
    (
        [TimeStamp] DESC,
        [InverterID] ASC
    ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF
    , IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON)
)

ฉันยังมีโพรซีเดอร์ตัวช่วยเล็ก ๆ นี้ซึ่งอนุญาตให้ฉันแทรกหรืออัพเดต (อัพเดตเมื่อมีข้อขัดแย้ง) ด้วยคำสั่ง MERGE:

CREATE PROCEDURE [InsertOrUpdateInverterData]
    @InverterID bigint, @TimeStamp datetime
    , @ValueA decimal(18,2), @ValueB decimal(18,2)
AS
BEGIN
    MERGE [InverterData] AS TARGET
        USING (VALUES (@InverterID, @TimeStamp, @ValueA, @ValueB))
        AS SOURCE ([InverterID], [TimeStamp], [ValueA], [ValueB])
        ON TARGET.[InverterID] = @InverterID AND TARGET.[TimeStamp] = @TimeStamp
    WHEN MATCHED THEN
        UPDATE
        SET [ValueA] = SOURCE.[ValueA], [ValueB] = SOURCE.[ValueB]              
    WHEN NOT MATCHED THEN
        INSERT ([InverterID], [TimeStamp], [ValueA], [ValueB]) 
        VALUES (SOURCE.[InverterID], SOURCE.[TimeStamp], SOURCE.[ValueA], SOURCE.[ValueB]);
END

การใช้

ตอนนี้ฉันได้เรียกใช้อินสแตนซ์ของบริการบนเซิร์ฟเวอร์หลายเครื่องที่มีการอัปเดตจำนวนมากโดยการเรียก[InsertOrUpdateInverterData]ขั้นตอนอย่างรวดเร็ว

นอกจากนี้ยังมีเว็บไซต์ที่ใช้แบบสอบถามบน[InverterData]ตาราง

ปัญหา

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

ความพยายามในการ

ฉันเลือก SELECT บางตัวบน[sys.dm_tran_locks]โต๊ะเพื่อหากระบวนการล็อคเช่นนี้

SELECT
tl.request_session_id,
wt.blocking_session_id,
OBJECT_NAME(p.OBJECT_ID) BlockedObjectName,
h1.TEXT AS RequestingText,
h2.TEXT AS BlockingText,
tl.request_mode

FROM sys.dm_tran_locks AS tl

INNER JOIN sys.dm_os_waiting_tasks AS wt ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.partitions AS p ON p.hobt_id = tl.resource_associated_entity_id
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
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

นี่คือผลลัพธ์ที่ได้:

ป้อนคำอธิบายรูปภาพที่นี่

S = แชร์แล้ว เซสชันการพักจะได้รับการเข้าถึงทรัพยากรร่วมกัน

คำถาม

ทำไม SELECTs ถูกบล็อกโดย[InsertOrUpdateInverterData]โพรซีเดอร์ที่ใช้คำสั่ง MERGE เท่านั้น?

ฉันต้องใช้ธุรกรรมบางประเภทกับโหมดแยกที่กำหนดไว้ภายใน[InsertOrUpdateInverterData]หรือไม่

อัปเดต 1 (เกี่ยวข้องกับคำถามจาก @Paul)

ฐานการรายงานภายในเซิร์ฟเวอร์ MS-SQL เกี่ยวกับ[InsertOrUpdateInverterData]สถิติต่อไปนี้:

  • เวลา CPU เฉลี่ย: 0.12ms
  • กระบวนการอ่านเฉลี่ย: 5.76 ต่อ / s
  • กระบวนการเขียนเฉลี่ย: 0.4 ต่อ / s

ดูเหมือนว่าคำสั่ง MERGE ส่วนใหญ่จะยุ่งอยู่กับการอ่านที่จะล็อคตาราง! (?)

อัปเดต 2 (เกี่ยวข้องกับคำถามจาก @Paul)

[InverterData]ตารางต่อไปนี้ได้มีการจัดเก็บข้อมูลสถิติ:

  • พื้นที่ข้อมูล: 26,901.86 MB
  • จำนวนแถว: 131,827,749
  • แบ่งพาร์ติชันแล้ว: จริง
  • จำนวนพาร์ติชัน: 62

นี่คือชุดผลลัพธ์sp_WhoIsActive (allmost) ที่สมบูรณ์:

SELECT คำสั่ง

  • dd hh: mm: ss.mss: 00 00: 01: 01.930
  • session_id: 73
  • wait_info: (12629ms) LCK_M_S
  • CPU: 198
  • บล็อค_session_id: 146
  • อ่าน: 99,368
  • เขียน: 0
  • สถานะ: ถูกระงับ
  • open_tran_count: 0

[InsertOrUpdateInverterData]คำสั่งการปิดกั้น

  • dd hh: mm: ss.mss: 00 00: 00: 00.330
  • session_id: 146
  • wait_info: NULL
  • CPU: 3,972
  • การปิดกั้น_session_id: NULL
  • อ่าน: 376,95
  • เขียน: 126
  • สถานะ: นอนหลับ
  • open_tran_count: 1

([TimeStamp] DESC, [InverterID] ASC)ดูเหมือนจะเป็นทางเลือกที่แปลกสำหรับดัชนีคลัสเตอร์ ฉันหมายถึงDESCส่วนหนึ่ง
ypercubeᵀᴹ

ฉันเข้าใจจุดของคุณ: ดัชนีแบบกลุ่ม DESC การแทรกข้อมูลจะบังคับให้สร้างตารางใหม่ต่อท้าย ... สุนัขประสิทธิภาพ จะล็อคตารางในขณะที่การสร้างใหม่เกิดขึ้น ... ใช่ โดย Jove คุณมีมัน โครงสร้างเป็นสาเหตุของการล็อคมากกว่าล็อค
Alocyte

คำตอบ:


12

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

MERGE [dbo].[InverterData] WITH (SERIALIZABLE) AS [TARGET]

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

คำถามหลัก

เหตุใดจึงSELECTsถูกบล็อกโดยขั้นตอน [InsertOrUpdateInverterData] ที่ใช้MERGEคำสั่งเท่านั้น

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

MERGEข้อมูลงบปรับเปลี่ยนจึงจะได้รับการปรับปรุงหรือ S (U) ล็อคเมื่อตำแหน่งข้อมูลเพื่อการเปลี่ยนแปลงซึ่งจะถูกแปลงเป็นพิเศษ (X) ล็อคเพียงก่อนที่จะดำเนินการปรับเปลี่ยนที่เกิดขึ้นจริง ต้องล็อค U และ X ทั้งสองจนจบธุรกรรม

สิ่งนี้เป็นจริงภายใต้ทุกระดับการแยกยกเว้นการแยกสแนปชอตในแง่ดี(SI) ที่ไม่ต้องสับสนกับการอ่านที่กำหนดเวอร์ชันหรือที่รู้จักกันว่าการอ่านสแน็ปช็อตการแยกที่มุ่งมั่น (RCSI)

ไม่มีสิ่งใดในคำถามของคุณที่แสดงว่าเซสชันกำลังรอการล็อก S ถูกบล็อกโดยเซสชันที่ถือล็อค U ล็อคเหล่านี้จะเข้ากันได้ การบล็อคใด ๆ นั้นเกือบจะเกิดจากการบล็อค X lock ที่ถืออยู่ นี่อาจเป็นเรื่องยุ่งยากเล็กน้อยในการจับภาพเมื่อมีการใช้การล็อคระยะสั้นจำนวนมากแปลงและเปิดตัวในช่วงเวลาสั้น ๆ

open_tran_count: 1บนคำสั่ง InsertOrUpdateInverterData เป็นมูลค่าการตรวจสอบ แม้ว่าคำสั่งจะไม่ได้ทำงานนานมากคุณควรตรวจสอบว่าคุณไม่มีธุรกรรมที่มี (ในแอปพลิเคชันหรือขั้นตอนการจัดเก็บระดับสูงกว่า) ที่มีความยาวเกินความจำเป็น แนวทางปฏิบัติที่ดีที่สุดคือการทำธุรกรรมให้สั้นที่สุด นี่อาจจะไม่มีอะไร แต่คุณควรตรวจสอบอย่างแน่นอน

โซลูชั่นที่มีศักยภาพ

ตามที่ Kin แนะนำไว้ในความคิดเห็นคุณสามารถเปิดใช้งานระดับการแยกการกำหนดเวอร์ชันแถว (RCSI หรือ SI) ในฐานข้อมูลนี้ RCSI ใช้บ่อยที่สุดเนื่องจากโดยทั่วไปไม่ต้องการการเปลี่ยนแปลงแอปพลิเคชันมากมาย เมื่อเปิดใช้งานแล้วระดับการแยกที่อ่านได้ที่เป็นค่าเริ่มต้นจะใช้รุ่นแถวแทนการล็อค S เพื่ออ่านดังนั้นการปิดกั้น SX จะลดลงหรือตัดออก การดำเนินการบางอย่าง (เช่นการตรวจสอบ foreign key) ยังคงได้รับ S lock ภายใต้ RCSI

โปรดทราบว่าเวอร์ชันแถวนั้นใช้พื้นที่ tempdb โดยพูดตามสัดส่วนกับอัตรากิจกรรมการเปลี่ยนแปลงและความยาวของธุรกรรม คุณจะต้องทดสอบการใช้งานของคุณอย่างละเอียดภายใต้ภาระเพื่อทำความเข้าใจและวางแผนผลกระทบของ RCSI (หรือ SI) ในกรณีของคุณ

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

หมายเหตุ:ซึ่งแตกต่างจาก RCSI (ซึ่งครั้งหนึ่งเคยเปิดใช้งานนำไปใช้กับการทำธุรกรรมทั้งหมดทำงานที่มุ่งมั่นในการอ่าน) SI SET TRANSACTION ISOLATION SNAPSHOT;จะต้องมีการขออย่างชัดเจนโดยใช้

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

ในที่สุดคุณควรตรวจสอบให้แน่ใจว่าอินสแตนซ์ของคุณได้รับการแก้ไขใน SQL Server 2008 Service Pack 4


0

เจียมฉันจะไม่ใช้การผสาน ฉันจะไปกับ IF Exists (UPDATE) ELSE (INSERT) - คุณมีคีย์คลัสเตอร์ที่มีสองคอลัมน์ที่คุณใช้เพื่อระบุแถวดังนั้นจึงเป็นการทดสอบที่ง่าย

คุณพูดถึงการแทรกขนาดใหญ่และยังทำ 1 ต่อ 1 ... คิดว่าการแบทช์ข้อมูลในตารางการแสดงละครและการใช้POWER OVERWHELMINGชุดข้อมูล SQL กำลังตั้งค่าให้ทำมากกว่า 1 การปรับปรุง / แทรกในเวลา? เช่นมีการทดสอบเป็นประจำสำหรับเนื้อหาในตารางการจัดอันดับและคว้า 10,000 อันดับแรกในแต่ละครั้งแทนที่จะเป็น 1 ครั้ง ...

ฉันจะทำสิ่งนี้ในการอัปเดตของฉัน

DECLARE @Set TABLE (StagingKey, ID,DATE)
INSERT INTO @Set
UPDATE Staging 
SET InProgress = 1
OUTPUT StagingKey, Staging.ID, Staging.Date
WHERE InProgress = 0
AND StagingID IN (SELECT TOP (100000) StagingKey FROM Staging WHERE inProgress = 0 ORDER BY StagingKey ASC ) --FIFO

DECLARE @Temp 
INSERT INTO @TEMP 
UPDATE [DEST] SET Value = Staging.Value [whatever]
OUTPUT INSERTED.ID, DATE [row identifiers]
FROM [DEST] 
JOIN [STAGING]
JOIN [@SET]; 
INSERT INTO @TEMP 
INSERT [DEST] 
SELECT
OUTPUT INSERT.ID, DATE [row identifiers] 
FROM [STAGING] 
JOIN [@SET] 
LEFT JOIN [DEST]

UPDATE Staging
SET inProgress = NULL
FROM Staging 
JOIN @set
ON @Set.Key = Staging.Key
JOIN @temp
ON @temp.id = @set.ID
AND @temp.date = @set.Date

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

while exists (inProgress is null) 
delete top (100) from staging where inProgress is null 

เพื่อล้างตาราง staging

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