หากฉันเข้าใจการร้องขออย่างถูกต้องเป้าหมายคือการลบชุดของแถวในขณะที่ในเวลาเดียวกันการดำเนินการ DML จะเกิดขึ้นในแถวทั่วทั้งตาราง เป้าหมายคือการลบแบทช์; อย่างไรก็ตามหากแถวต้นแบบที่อยู่ภายในช่วงที่กำหนดโดยชุดงานดังกล่าวถูกล็อคเราจะต้องข้ามชุดนั้นและย้ายไปยังชุดถัดไป จากนั้นเราจะต้องกลับไปที่กลุ่มที่ไม่ได้ถูกลบไปก่อนหน้านี้และลองตรรกะการลบเดิมของเราอีกครั้ง เราต้องทำซ้ำรอบนี้จนกว่าแบทช์แถวที่ต้องการทั้งหมดจะถูกลบ
ตามที่ได้กล่าวมาแล้วมันมีเหตุผลที่จะใช้คำแนะนำ READPAST และระดับการแยก READPETTED (ค่าเริ่มต้น) เพื่อข้ามช่วงที่ผ่านมาซึ่งอาจมีแถวที่ถูกบล็อก ฉันจะไปอีกขั้นหนึ่งและแนะนำให้ใช้ระดับการแยกแบบ SERIALIZABLE และการลบแบบแทะ
SQL Server ใช้การล็อก Key-Range เพื่อปกป้องช่วงของแถวที่รวมอยู่ในชุดระเบียนที่ถูกอ่านโดยคำสั่ง Transact-SQL ในขณะที่ใช้ระดับการแยกธุรกรรมที่สามารถทำให้เป็นลำดับได้ ... หาข้อมูลเพิ่มเติมได้ที่นี่:
https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
ด้วยการลบ nibbling เป้าหมายของเราคือการแยกช่วงของแถวและตรวจสอบให้แน่ใจว่าไม่มีการเปลี่ยนแปลงเกิดขึ้นกับแถวเหล่านั้นในขณะที่เรากำลังลบพวกเขากล่าวคือเราไม่ต้องการอ่านหรือแทรก phantom ระดับการแยกแบบอนุกรมได้หมายถึงการแก้ปัญหานี้
ก่อนที่ฉันจะแสดงวิธีแก้ปัญหาของฉันฉันต้องการเพิ่มว่าฉันไม่แนะนำให้เปลี่ยนระดับการแยกฐานข้อมูลเริ่มต้นของคุณเป็น SERIALIZABLE และฉันไม่แนะนำให้โซลูชันของฉันดีที่สุด ฉันแค่ต้องการนำเสนอมันและดูว่าเราจะไปจากที่นี่
หมายเหตุเกี่ยวกับการดูแลรักษาบ้าน:
- SQL Server รุ่นที่ฉันใช้คือ Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- ฐานข้อมูลทดสอบของฉันใช้โมเดลการกู้คืนแบบเต็ม
ในการเริ่มต้นการทดสอบของฉันฉันจะตั้งค่าฐานข้อมูลทดสอบตารางตัวอย่างและฉันจะเติมตารางด้วย 2,000,000 แถว
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
ALTER DATABASE [test] SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
c1 BIGINT IDENTITY (1,1) NOT NULL
, c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows
INSERT INTO dbo.tbl
SELECT TOP 2000
number
FROM
master..spt_values
ORDER BY
number
GO 1000
ณ จุดนี้เราจะต้องมีดัชนีตั้งแต่หนึ่งตัวขึ้นไปซึ่งกลไกการล็อคของระดับการแยกแบบ SERIALIZABLE สามารถกระทำได้
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2
ON dbo.tbl (c2);
GO
ตอนนี้ให้เราตรวจสอบเพื่อดูว่ามีการสร้าง 2,000,000 แถวของเรา
SELECT
COUNT(*)
FROM
tbl;
ดังนั้นเรามีฐานข้อมูลตารางดัชนีและแถวของเรา ดังนั้นให้เราตั้งค่าการทดสอบสำหรับการลบแทะ ก่อนอื่นเราต้องตัดสินใจว่าจะสร้างกลไกการลบแบบแทปได้ดีที่สุดเพียงใด
DECLARE
@BatchSize INT = 100
, @LowestValue BIGINT = 20000
, @HighestValue BIGINT = 20010
, @DeletedRowsCount BIGINT = 0
, @RowCount BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE @DeletedRowsCount < ( @HighestValue - @LowestValue )
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
DELETE
FROM
dbo.tbl
WHERE
c1 IN (
SELECT TOP (@BatchSize)
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN @LowestValue AND @HighestValue
ORDER BY
c1
);
SET @RowCount = ROWCOUNT_BIG();
COMMIT TRANSACTION;
SET @DeletedRowsCount += @RowCount;
WAITFOR DELAY '000:00:00.025';
CHECKPOINT;
END;
อย่างที่คุณเห็นฉันวางธุรกรรมที่ชัดเจนไว้ในขณะที่ลูป หากคุณต้องการ จำกัด การบันทึกวูบวาบคุณสามารถวางไว้นอกลูปได้ นอกจากนี้เนื่องจากเราอยู่ในรูปแบบการกู้คืนแบบเต็มคุณอาจต้องการสร้างการสำรองข้อมูลบันทึกธุรกรรมบ่อยขึ้นในขณะที่เรียกใช้การดำเนินการลบ nibbling ของคุณเพื่อให้แน่ใจว่าบันทึกธุรกรรมของคุณสามารถป้องกันไม่ให้เติบโตอย่างก้าวร้าว
ดังนั้นฉันมีเป้าหมายสองสามข้อในการตั้งค่านี้ ก่อนอื่นฉันต้องการล็อคช่วงกุญแจ ดังนั้นฉันพยายามเก็บแบทช์ให้เล็กที่สุดเท่าที่จะทำได้ ฉันยังไม่ต้องการที่จะส่งผลกระทบในทางลบต่อการเกิดพร้อมกันในตาราง "มโหฬาร" ของฉัน ดังนั้นฉันต้องการล็อคและปล่อยให้เร็วที่สุดเท่าที่จะทำได้ ดังนั้นฉันขอแนะนำให้คุณทำให้แบทช์ของคุณมีขนาดเล็ก
ตอนนี้ฉันต้องการให้ตัวอย่างสั้น ๆ ของรูทีนการลบที่ใช้งานจริง เราต้องเปิดหน้าต่างใหม่ภายใน SSMS และลบหนึ่งแถวออกจากตารางของเรา ฉันจะทำสิ่งนี้ภายในธุรกรรมโดยนัยโดยใช้ระดับการแยกเริ่มต้นของ READ COMMITTED
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
แถวนี้ถูกลบจริงหรือไม่?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
ใช่มันถูกลบไปแล้ว
ตอนนี้เพื่อที่จะเห็นล็อคของเราให้เราเปิดหน้าต่างใหม่ภายใน SSMS และเพิ่มข้อมูลโค้ดหรือสอง ฉันกำลังใช้ sp_whoisactive ของ Adam Mechanic ซึ่งสามารถพบได้ที่นี่: sp_whoisactive
SELECT
DB_NAME(resource_database_id) AS DatabaseName
, resource_type
, request_mode
FROM
sys.dm_tran_locks
WHERE
DB_NAME(resource_database_id) = 'test'
AND resource_type = 'KEY'
ORDER BY
request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
ตอนนี้เราพร้อมที่จะเริ่ม ในหน้าต่าง SSMS ใหม่ให้เราเริ่มทำธุรกรรมอย่างชัดเจนซึ่งจะพยายามแทรกแถวที่เราลบอีกครั้ง ในเวลาเดียวกันเราจะทำการปิดการดำเนินการลบแทปของเรา
รหัสแทรก:
BEGIN TRANSACTION
SET IDENTITY_INSERT dbo.tbl ON;
INSERT INTO dbo.tbl
( c1 , c2 )
VALUES
( 20005 , 1 );
SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
ให้เราเริ่มการดำเนินการทั้งสองเริ่มด้วยการแทรกและตามด้วยการลบของเรา เราสามารถเห็นกุญแจล็อคช่วงและล็อคพิเศษ
ส่วนแทรกสร้างล็อคเหล่านี้:
การลบ / เลือก nibbling ถือล็อคเหล่านี้:
ส่วนแทรกของเราบล็อกการลบของเราตามที่คาดไว้:
ตอนนี้ให้เรากระทำธุรกรรมแทรกและดูว่ามีอะไรเกิดขึ้น
และตามที่คาดไว้การทำธุรกรรมทั้งหมดเสร็จสมบูรณ์ ตอนนี้เราต้องตรวจสอบเพื่อดูว่าส่วนแทรกนั้นเป็นภาพหลอนหรือไม่หรือการลบลบออกเช่นกัน
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
อันที่จริงการแทรกถูกลบ; ดังนั้นจึงไม่อนุญาตการแทรกภาพหลอน
โดยสรุปแล้วฉันคิดว่าความตั้งใจที่แท้จริงของแบบฝึกหัดนี้คือไม่ลองและติดตามทุกแถวหน้าหรือล็อคระดับตารางและพยายามตรวจสอบว่าองค์ประกอบของแบทช์ถูกล็อคและจะต้องใช้การลบของเราเพื่อ รอ. นั่นอาจเป็นเจตนาของผู้ถาม อย่างไรก็ตามงานนั้นเป็นเรื่องยากมากและไม่สามารถปฏิบัติได้จริงหากเป็นไปไม่ได้ เป้าหมายที่แท้จริงคือเพื่อให้แน่ใจว่าไม่มีปรากฏการณ์ที่ไม่พึงประสงค์เกิดขึ้นเมื่อเราแยกช่วงของแบทช์ของเราด้วยการล็อคของเราเองแล้วนำหน้าเพื่อลบแบทช์ ระดับการแยกแบบ SERIALIZABLE บรรลุวัตถุประสงค์นี้ กุญแจสำคัญคือการทำให้ไส้ปากกาของคุณเล็กบันทึกการทำธุรกรรมของคุณภายใต้การควบคุมและกำจัดปรากฏการณ์ที่ไม่พึงประสงค์
หากคุณต้องการความเร็วอย่าสร้างตารางที่มีขนาดใหญ่มากซึ่งไม่สามารถแบ่งพาร์ติชันได้ดังนั้นจึงไม่สามารถใช้การสลับพาร์ติชันเพื่อผลลัพธ์ที่เร็วที่สุด กุญแจสำคัญในความเร็วคือการแบ่งและการขนาน กุญแจสู่ความทุกข์ยากคือการกัดและการล็อคชีวิต
โปรดแจ้งให้เราทราบว่าคุณคิดอย่างไร
ฉันสร้างตัวอย่างเพิ่มเติมของระดับการแยกแบบ SERIALIZABLE ในการดำเนินการ พวกเขาควรจะอยู่ที่ลิงค์ด้านล่าง
ลบการทำงาน
แทรกการดำเนินงาน
การดำเนินการด้านความเท่าเทียมกัน - ล็อคช่วงคีย์บนค่าคีย์ถัดไป
Equality Operations - Singleton Fetch ของข้อมูลที่มีอยู่
Equality Operations - Singleton Fetch ของข้อมูลที่ไม่มีอยู่
การดำเนินการไม่เท่าเทียมกัน - ล็อคช่วง Key-Range และค่าคีย์ถัดไป