คำถามนี้เกี่ยวข้องกับกระทู้ในฟอรัมนี้
ใช้ SQL Server 2008 Developer Edition บนเวิร์กสเตชันของฉันและคลัสเตอร์เครื่องเสมือนสองโหนด Enterprise Edition ซึ่งฉันอ้างถึง "อัลฟ่าคลัสเตอร์"
เวลาที่ใช้ในการลบแถวที่มีคอลัมน์ varbinary (สูงสุด) เกี่ยวข้องโดยตรงกับความยาวของข้อมูลในคอลัมน์นั้น นั่นอาจฟังดูเป็นเรื่องง่ายในตอนแรก แต่หลังจากการตรวจสอบมันขัดแย้งกับความเข้าใจของฉันเกี่ยวกับวิธีที่ SQL Server ลบแถวทั่วไปและจัดการกับข้อมูลประเภทนี้จริง ๆ
ปัญหาเกิดจากปัญหาการหมดเวลาในการลบ (> 30 วินาที) ที่เราเห็นในเว็บแอปพลิเคชัน. NET ของเรา แต่ฉันได้ทำให้มันง่ายขึ้นเพื่อการอภิปรายนี้
เมื่อลบเรกคอร์ด SQL Server จะทำเครื่องหมายว่าเป็นโกสต์ที่จะล้างข้อมูลโดย Ghost Cleanup Task ในเวลาต่อมาหลังจากการทำธุรกรรม (ดูที่บล็อกของ Paul Randal ) ในการทดสอบการลบสามแถวที่มีข้อมูล 16 KB, 4 MB และ 50 MB ในคอลัมน์ varbinary (สูงสุด) ตามลำดับฉันเห็นสิ่งนี้เกิดขึ้นบนหน้าเว็บที่มีส่วนของข้อมูลในแถวรวมทั้งในธุรกรรม เข้าสู่ระบบ
สิ่งที่ดูเหมือนแปลกสำหรับฉันคือการวาง X ล็อคในหน้าข้อมูล LOB ทั้งหมดในระหว่างการลบและหน้าถูกจัดสรรคืนใน PFS ฉันเห็นสิ่งนี้ในบันทึกธุรกรรมเช่นเดียวกับsp_lock
และผลลัพธ์ของdm_db_index_operational_stats
DMV ( page_lock_count
)
สิ่งนี้จะสร้างคอขวด I / O บนเวิร์กสเตชันและคลัสเตอร์อัลฟาของเราหากหน้าเหล่านั้นไม่ได้อยู่ในแคชแคช ในความเป็นจริงแล้วpage_io_latch_wait_in_ms
DMV เดียวกันนั้นเป็นระยะเวลาทั้งหมดของการลบและpage_io_latch_wait_count
สอดคล้องกับจำนวนหน้าที่ถูกล็อก สำหรับไฟล์ขนาด 50 MB บนเวิร์กสเตชันของฉันแปลได้นานกว่า 3 วินาทีเมื่อเริ่มต้นด้วยแคชบัฟเฟอร์ ( checkpoint
/ dbcc dropcleanbuffers
) ที่ว่างเปล่าและฉันไม่สงสัยเลยว่ามันจะนานกว่าสำหรับการแตกแฟรกเมนต์หนักและภายใต้การโหลด
ฉันพยายามทำให้แน่ใจว่าไม่เพียงจัดสรรพื้นที่ในแคชเท่านั้น ฉันอ่านข้อมูลในแถว 2 GB จากแถวอื่นก่อนที่จะทำการลบแทนcheckpoint
วิธีการซึ่งมากกว่าที่กำหนดไว้ในกระบวนการ SQL Server ไม่แน่ใจว่าเป็นการทดสอบที่ถูกต้องหรือไม่เนื่องจากฉันไม่รู้ว่า SQL Server สับเปลี่ยนข้อมูลได้อย่างไร ฉันคิดว่ามันจะผลักดันคนเก่าให้เข้ากับคนใหม่เสมอ
นอกจากนี้ยังไม่ได้ปรับเปลี่ยนหน้า dm_os_buffer_descriptors
ฉันนี้สามารถมองเห็นได้ด้วย หน้าสะอาดหลังจากลบในขณะที่จำนวนหน้าที่แก้ไขน้อยกว่า 20 สำหรับการลบทั้งเล็กกลางและใหญ่ทั้งสาม ฉันยังได้เปรียบเทียบผลลัพธ์ของDBCC PAGE
การสุ่มตัวอย่างของหน้าที่ค้นหาและไม่มีการเปลี่ยนแปลง (เฉพาะALLOCATED
บิตที่ถูกลบออกจาก PFS) มันเป็นเพียงการจัดสรรคืนพวกเขา
เพื่อพิสูจน์เพิ่มเติมว่าการค้นหาหน้า / deallocations ทำให้เกิดปัญหาฉันลองทดสอบเดียวกันโดยใช้คอลัมน์ filestream แทน vanilla varbinary (สูงสุด) การลบเป็นเวลาคงที่โดยไม่คำนึงถึงขนาด LOB
ดังนั้นก่อนอื่นคำถามทางวิชาการของฉัน:
- ทำไม SQL Server จำเป็นต้องค้นหาหน้าข้อมูล LOB ทั้งหมดเพื่อล็อค X นั่นเป็นเพียงรายละเอียดว่าล็อคถูกแสดงในหน่วยความจำได้อย่างไร (เก็บไว้ที่หน้าอย่างใด)? สิ่งนี้ทำให้ผลกระทบของ I / O ขึ้นอยู่กับขนาดข้อมูลเป็นอย่างมากหากไม่ได้แคชอย่างสมบูรณ์
- ทำไม X ถึงล็อคอยู่เพียงเพื่อยกเลิกการจัดสรรพวกเขา? มันไม่เพียงพอที่จะล็อคเพียงแค่ดัชนีใบไม้ที่มีส่วนในแถวเนื่องจากการจัดสรรคืนไม่จำเป็นต้องแก้ไขหน้าตัวเอง? มีวิธีอื่นในการรับข้อมูล LOB ที่ล็อคป้องกันหรือไม่
- เหตุใดจึงยกเลิกการจัดสรรหน้าใหม่เลยเนื่องจากมีงานพื้นหลังที่อุทิศให้กับงานประเภทนี้อยู่แล้ว?
และที่สำคัญกว่านั้นคำถามที่ปฏิบัติได้ของฉัน:
- มีวิธีใดที่จะทำให้การลบทำงานแตกต่างกันหรือไม่? เป้าหมายของฉันคือการลบเวลาคงที่โดยไม่คำนึงถึงขนาดคล้ายกับ filestream ที่การล้างข้อมูลใด ๆ เกิดขึ้นในพื้นหลังหลังจากความจริง มันเป็นสิ่งที่กำหนดค่าหรือไม่? ฉันกำลังเก็บของแปลก ๆ อยู่หรือเปล่า?
นี่คือวิธีการทำซ้ำการทดสอบที่อธิบาย (ดำเนินการผ่านหน้าต่างแบบสอบถาม SSMS):
CREATE TABLE [T] (
[ID] [uniqueidentifier] NOT NULL PRIMARY KEY,
[Data] [varbinary](max) NULL
)
DECLARE @SmallID uniqueidentifier
DECLARE @MediumID uniqueidentifier
DECLARE @LargeID uniqueidentifier
SELECT @SmallID = NEWID(), @MediumID = NEWID(), @LargeID = NEWID()
-- May want to keep these IDs somewhere so you can use them in the deletes without var declaration
INSERT INTO [T] VALUES (@SmallID, CAST(REPLICATE(CAST('a' AS varchar(max)), 16 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@MediumID, CAST(REPLICATE(CAST('a' AS varchar(max)), 4 * 1024 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@LargeID, CAST(REPLICATE(CAST('a' AS varchar(max)), 50 * 1024 * 1024) AS varbinary(max)))
-- Do this before test
CHECKPOINT
DBCC DROPCLEANBUFFERS
BEGIN TRAN
-- Do one of these deletes to measure results or profile
DELETE FROM [T] WHERE ID = @SmallID
DELETE FROM [T] WHERE ID = @MediumID
DELETE FROM [T] WHERE ID = @LargeID
-- Do this after test
ROLLBACK
นี่คือผลลัพธ์บางส่วนจากการทำโปรไฟล์การลบในเวิร์กสเตชันของฉัน:
| ประเภทคอลัมน์ | ลบขนาด | ระยะเวลา (ms) | อ่าน | เขียน | ซีพียู | -------------------------------------------------- ------------------ | VarBinary | 16 KB | 40 | 13 | 2 | 0 | | VarBinary | 4 MB | 952 | 2318 | 2 | 0 | | VarBinary | 50 MB | 2976 | 28594 | 1 | 62 | -------------------------------------------------- ------------------ | FileStream | 16 KB | 1 | 12 | 1 | 0 | | FileStream | 4 MB | 0 | 9 | 0 | 0 | | FileStream | 50 MB | 1 | 9 | 0 | 0 |
เราไม่สามารถใช้ filestream แทนได้เพราะ:
- การกระจายขนาดข้อมูลของเราไม่รับประกัน
- ในทางปฏิบัติเราเพิ่มข้อมูลในกลุ่มข้อมูลจำนวนมากและ filestream ไม่สนับสนุนการอัปเดตบางส่วน เราจะต้องออกแบบสิ่งนี้
อัปเดต 1
ทดสอบทฤษฎีที่ว่าข้อมูลกำลังถูกเขียนไปยังบันทึกธุรกรรมเป็นส่วนหนึ่งของการลบและดูเหมือนจะไม่เป็นเช่นนั้น ฉันกำลังทดสอบสิ่งนี้ไม่ถูกต้องหรือไม่? ดูด้านล่าง
SELECT MAX([Current LSN]) FROM fn_dblog(NULL, NULL)
--0000002f:000001d9:0001
BEGIN TRAN
DELETE FROM [T] WHERE ID = @ID
SELECT
SUM(
DATALENGTH([RowLog Contents 0]) +
DATALENGTH([RowLog Contents 1]) +
DATALENGTH([RowLog Contents 3]) +
DATALENGTH([RowLog Contents 4])
) [RowLog Contents Total],
SUM(
DATALENGTH([Log Record])
) [Log Record Total]
FROM fn_dblog(NULL, NULL)
WHERE [Current LSN] > '0000002f:000001d9:0001'
สำหรับไฟล์กว่า 5 1651 | 171860
ล้านบาทในขนาดนี้กลับมา
เพิ่มเติมฉันคาดหวังว่าหน้าตัวเองจะสกปรกหากข้อมูลถูกเขียนลงในบันทึก ดูเหมือนว่าจะมีการบันทึกการจัดสรรคืนเท่านั้นซึ่งตรงกับสิ่งที่สกปรกหลังจากลบ
อัปเดต 2
ฉันได้รับการตอบกลับจาก Paul Randal เขายืนยันความจริงที่ว่ามันต้องอ่านทุกหน้าเพื่อสำรวจต้นไม้และค้นหาว่าหน้าไหนที่จะจัดสรรคืนและระบุว่าไม่มีทางอื่นที่จะค้นหาหน้าไหน นี่เป็นคำตอบครึ่งหนึ่งสำหรับ 1 & 2 (แต่ไม่ได้อธิบายความจำเป็นในการล็อคข้อมูลนอกแถว แต่นั่นเป็นมันฝรั่งขนาดเล็ก)
คำถามที่ 3 ยังคงเปิดอยู่: เหตุใดจึงยกเลิกการจัดสรรหน้าใหม่หากมีงานพื้นหลังที่ต้องทำการล้างข้อมูลเพื่อลบ
และแน่นอนคำถามที่สำคัญทั้งหมด: มีวิธีลดพฤติกรรมการลบตามขนาดนี้โดยตรงหรือไม่ ฉันคิดว่านี่จะเป็นปัญหาที่พบบ่อยกว่านี้เว้นแต่เราจะเป็นคนเดียวที่จัดเก็บและลบแถว 50 MB ใน SQL Server ทุกคนที่ทำงานที่นี่มีงานเก็บขยะหรือไม่