ฉันสามารถอ่านค่า SQL Server Identity ตามลำดับได้หรือไม่?


24

TL: DR:คำถามด้านล่างนี้ลดลงเหลือ: เมื่อแทรกแถวจะมีหน้าต่างของโอกาสระหว่างการสร้างIdentityค่าใหม่และการล็อคกุญแจแถวที่สอดคล้องกันในดัชนีคลัสเตอร์ซึ่งผู้สังเกตการณ์ภายนอกจะเห็นที่ใหม่กว่า Identityมูลค่าแทรกโดยการทำธุรกรรมพร้อมกันหรือไม่ (ใน SQL Server)

รุ่นโดยละเอียด

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

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

ตัวอย่าง: เมื่อแทรกแถวที่มีค่าเอกลักษณ์ 1, 2, 3, 4 และ 5 ผู้อ่านจะต้องไม่เห็นแถวที่มีค่า 5 ก่อนที่จะเห็นแถวที่มีค่า 4 การทดสอบแสดงให้เห็นว่าแบบสอบถามซึ่งมีORDER BY CheckpointSequenceข้อ ( และWHERE CheckpointSequence > -1ประโยค) บล็อกได้อย่างน่าเชื่อถือเมื่อใดก็ตามที่ต้องอ่านแถว 4 แต่ยังไม่ได้ยืนยันแม้ว่าแถวที่ 5 ได้รับการยืนยันแล้ว

ฉันเชื่อว่าอย่างน้อยในทางทฤษฎีอาจมีสภาพการแข่งขันที่นี่ซึ่งอาจทำให้สมมติฐานนี้แตก น่าเสียดายที่เอกสารเกี่ยวกับIdentityไม่ได้พูดมากเกี่ยวกับวิธีการIdentityทำงานในบริบทของการทำธุรกรรมหลาย ๆ อย่างพร้อมกันเพียง แต่บอกว่า "แต่ละค่าใหม่จะถูกสร้างขึ้นบนพื้นฐานของการเพาะเมล็ด & การเพิ่มขึ้น" และ "แต่ละค่าใหม่สำหรับการทำธุรกรรมเฉพาะนั้นแตกต่างจากการทำธุรกรรมพร้อมกันอื่น ๆ ในตาราง" ( MSDN )

เหตุผลของฉันคือมันต้องทำงานอย่างนี้:

  1. ธุรกรรมเริ่มต้น (ไม่ว่าจะโดยชัดแจ้งหรือโดยนัย)
  2. สร้างค่าเอกลักษณ์ (X)
  3. การล็อคแถวที่เกี่ยวข้องถูกนำมาใช้กับดัชนีคลัสเตอร์ตามค่าข้อมูลประจำตัว (ยกเว้นกรณีที่การเลื่อนระดับการล็อคเริ่มขึ้นซึ่งในกรณีนี้ทั้งตารางถูกล็อค)
  4. แถวถูกแทรก
  5. ธุรกรรมมีความมุ่งมั่น (อาจเป็นจำนวนมากในเวลาต่อมา) ดังนั้นการล็อคจะถูกลบอีกครั้ง

ฉันคิดว่าระหว่างขั้นตอนที่ 2 ถึง 3 มีหน้าต่างเล็ก ๆ

  • เซสชันที่เกิดขึ้นพร้อมกันสามารถสร้างค่าเอกลักษณ์ถัดไป (X + 1) และดำเนินการตามขั้นตอนที่เหลือทั้งหมด
  • ดังนั้นการอนุญาตให้ผู้อ่านเข้ามาถึงจุดนั้นในเวลานั้นเพื่ออ่านค่า X + 1 ซึ่งไม่มีค่าของ X

แน่นอนความน่าจะเป็นของเรื่องนี้ดูต่ำมาก แต่ยัง - มันอาจเกิดขึ้น หรือมันได้หรือไม่

(หากคุณสนใจในบริบท: นี่คือการใช้งานSQL Persistence Engineของ NEventStore NEventStore จะใช้ที่เก็บเหตุการณ์แบบผนวกเท่านั้นที่ทุกเหตุการณ์จะได้รับหมายเลขลำดับจุดตรวจใหม่จากน้อยไปมากลูกค้าอ่านเหตุการณ์จากที่เก็บเหตุการณ์ที่สั่งโดยจุดตรวจ เพื่อทำการคำนวณทุกประเภทเมื่อเหตุการณ์ที่มีจุดตรวจถูกประมวลผลแล้วลูกค้าจะพิจารณาเฉพาะเหตุการณ์ที่ "ใหม่กว่า" เช่นเหตุการณ์ที่มีจุดตรวจสอบ X + 1 ขึ้นไปดังนั้นจึงเป็นสิ่งสำคัญที่เหตุการณ์จะไม่ถูกข้ามไป อย่างที่ไม่เคยได้รับการพิจารณาอีกครั้งในขณะนี้ฉันกำลังพยายามตรวจสอบว่าการIdentityใช้งานจุดตรวจสอบแบบพื้นฐานตรงตามข้อกำหนดนี้หรือไม่นี่คือคำสั่ง SQL ที่แน่นอนที่ใช้ : Schema , แบบสอบถามของนักเขียน ,ข้อความค้นหาของผู้อ่าน )

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

  • เมื่อเห็นค่าลำดับจุดตรวจสอบ X + 1 ก่อนที่จะเห็น X ให้ปิด X + 1 แล้วลองอีกครั้งในภายหลัง อย่างไรก็ตามเนื่องจากIdentityแน่นอนสามารถสร้างช่องว่าง (เช่นเมื่อธุรกรรมถูกย้อนกลับ) X อาจไม่เคยมา
  • ดังนั้นวิธีการเดียวกัน แต่ยอมรับช่องว่างหลังจาก n มิลลิวินาที อย่างไรก็ตามฉันควรถือว่าค่าของ n คืออะไร?

มีความคิดที่ดีกว่านี้ไหม?


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

@SoleDBAGuy ลำดับจะไม่ทำให้สภาพการแข่งขันที่ฉันอธิบายข้างต้นมีแนวโน้มมากขึ้น? ฉันสร้างค่าลำดับใหม่ X (แทนที่ขั้นตอนที่ 2 ด้านบน) จากนั้นใส่แถว (ขั้นตอนที่ 3 และ 4) ระหว่าง 2 และ 3 มีความเป็นไปได้ที่คนอื่นอาจสร้างค่าลำดับต่อไป X + 1, ยอมรับและผู้อ่านอ่านค่า X + 1 ก่อนที่ฉันจะแทรกแถวของฉันด้วยค่าลำดับ X
Fabian Schmied

คำตอบ:


26

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

ใช่.

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

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

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

  1. เซสชั่น 1 ได้รับค่าตัวตน (3)
  2. ช่วงที่ 2 รับค่าตัวตน (4)
  3. เซสชั่น 2 ทำการแทรกและกระทำ (เพื่อให้มองเห็นแถวที่ 4 ได้อย่างสมบูรณ์)
  4. เซสชัน 1 ทำการแทรกและกระทำ (แถวที่ 3)

หลังจากขั้นตอนที่ 3 เคียวรีที่ใช้row_numberภายใต้การล็อกการอ่านที่คอมมิตส่งคืนค่าต่อไปนี้:

ภาพหน้าจอ

ในการติดตั้งของคุณสิ่งนี้จะส่งผลให้จุดตรวจสอบ ID 3 ถูกข้ามอย่างไม่ถูกต้อง

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

เพื่อความชัดเจนไม่มีการล็อคหรือวัตถุซิงโครไนซ์อื่น ๆ ที่ปกป้องค่าตัวตนหลังจากมีการจัดสรรและก่อนที่มันจะถูกใช้ ตัวอย่างเช่นหลังจากขั้นตอนที่ 1 ข้างต้นธุรกรรมที่เกิดขึ้นพร้อมกันสามารถเห็นค่าตัวตนใหม่โดยใช้ฟังก์ชั่น T-SQL เหมือนIDENT_CURRENTก่อนที่แถวจะมีอยู่ในตาราง (แม้ไม่มีข้อผูกมัด)

พื้นฐานไม่มีการรับประกันเกี่ยวกับค่าตัวตนมากกว่าเอกสาร :

  • ค่าใหม่แต่ละค่าจะถูกสร้างขึ้นตามเมล็ดพันธุ์ปัจจุบัน & การเพิ่มขึ้น
  • แต่ละค่าใหม่สำหรับธุรกรรมเฉพาะนั้นแตกต่างจากธุรกรรมอื่น ๆ ที่เกิดขึ้นพร้อมกันบนตาราง

นั่นมันจริงๆ

หากการประมวลผล FIFO ของธุรกรรมที่เข้มงวดนั้นเป็นไปได้ว่าคุณไม่มีทางเลือกนอกจากต้องซีเรียลไลซ์ด้วยตนเอง หากแอปพลิเคชันมีข้อกำหนดที่น้อยกว่าคุณมีตัวเลือกเพิ่มเติม คำถามไม่ชัดเจน 100% ในเรื่องนั้น แต่คุณอาจพบข้อมูลที่เป็นประโยชน์ในบทความรีมัส Rusanu ของการใช้ตารางเป็นคิว


7

ดังที่ Paul White ตอบถูกต้องอย่างแน่นอนมีความเป็นไปได้สำหรับแถวข้อมูลประจำตัวที่ "ข้าม" ชั่วคราว นี่เป็นเพียงส่วนเล็ก ๆ ของรหัสในการทำซ้ำกรณีนี้สำหรับคุณเอง

สร้างฐานข้อมูลและตารางทดสอบ:

create database IdentityTest
go
use IdentityTest
go
create table dbo.IdentityTest (ID int identity, c1 char(10))
create clustered index CI_dbo_IdentityTest_ID on dbo.IdentityTest(ID)

ทำการแทรกพร้อมกันและเลือกในตารางนี้ในโปรแกรมคอนโซล C #:

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Threading;

namespace IdentityTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var insertThreads = new List<Thread>();
            var selectThreads = new List<Thread>();

            //start threads for infinite inserts
            for (var i = 0; i < 100; i++)
            {
                insertThreads.Add(new Thread(InfiniteInsert));
                insertThreads[i].Start();
            }

            //start threads for infinite selects
            for (var i = 0; i < 10; i++)
            {
                selectThreads.Add(new Thread(InfiniteSelectAndCheck));
                selectThreads[i].Start();
            }
        }

        private static void InfiniteSelectAndCheck()
        {
            //infinite loop
            while (true)
            {
                //read top 2 IDs
                var cmd = new SqlCommand("select top(2) ID from dbo.IdentityTest order by ID desc")
                {
                    Connection = new SqlConnection("Server=localhost;Database=IdentityTest;Integrated Security=SSPI;Application Name=IdentityTest")
                };

                try
                {
                    cmd.Connection.Open();
                    var dr = cmd.ExecuteReader();

                    //read first row
                    dr.Read();
                    var row1 = int.Parse(dr["ID"].ToString());

                    //read second row
                    dr.Read();
                    var row2 = int.Parse(dr["ID"].ToString());

                    //write line if row1 and row are not consecutive
                    if (row1 - 1 != row2)
                    {
                        Console.WriteLine("row1=" + row1 + ", row2=" + row2);
                    }
                }
                finally
                {
                    cmd.Connection.Close();
                }
            }
        }

        private static void InfiniteInsert()
        {
            //infinite loop
            while (true)
            {
                var cmd = new SqlCommand("insert into dbo.IdentityTest (c1) values('a')")
                {
                    Connection = new SqlConnection("Server=localhost;Database=IdentityTest;Integrated Security=SSPI;Application Name=IdentityTest")
                };

                try
                {
                    cmd.Connection.Open();
                    cmd.ExecuteNonQuery();
                }
                finally
                {
                    cmd.Connection.Close();
                }
            }
        }
    }
}

คอนโซลนี้พิมพ์บรรทัดสำหรับทุกกรณีเมื่อหนึ่งในเธรดการอ่าน "คิดถึง" รายการ


1
รหัสดี แต่คุณจะตรวจสอบรหัสติดต่อกัน ( "// เขียนบรรทัดถ้า row1 และแถวไม่ต่อเนื่องกัน" ) อาจมีช่องว่างที่รหัสของคุณจะพิมพ์ ไม่ได้หมายความว่าช่องว่างเหล่านี้จะถูกเติมในภายหลัง
ypercubeᵀᴹ

1
เนื่องจากรหัสไม่ก่อให้เกิดสถานการณ์ที่IDENTITYจะทำให้เกิดช่องว่าง (เช่นย้อนกลับธุรกรรม) บรรทัดที่พิมพ์จริง ๆ แสดงค่า "ข้าม" (หรืออย่างน้อยพวกเขาก็ทำเมื่อฉันวิ่งและตรวจสอบบนเครื่องของฉัน) ตัวอย่างที่ดีมาก!
เฟเบียน Schmied

5

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

โดยทั่วไปช่องว่างอาจเกิดขึ้นได้หากคุณย้อนกลับการดำเนินการ INSERT (หรือลบแถวอย่างชัดเจน) และรายการที่ซ้ำกันสามารถเกิดขึ้นได้หากคุณตั้งค่าคุณสมบัติของตาราง IDENTITY_INSERT เป็น ON

ช่องว่างสามารถเกิดขึ้นได้เมื่อ:

  1. บันทึกจะถูกลบ
  2. เกิดข้อผิดพลาดขณะพยายามแทรกระเบียนใหม่ (ย้อนกลับ)
  3. การอัพเดต / การแทรกที่มีค่าที่ชัดเจน (ตัวเลือก identity_insert)
  4. ค่าที่เพิ่มขึ้นมากกว่า 1
  5. ธุรกรรมย้อนกลับ

คุณสมบัติข้อมูลประจำตัวในคอลัมน์ไม่เคยรับประกัน:

•เอกลักษณ์

•ค่าติดต่อกันภายในการทำธุรกรรม หากค่าต้องต่อเนื่องกันธุรกรรมนั้นควรใช้การล็อกแบบเอกสิทธิ์เฉพาะบุคคลบนตารางหรือใช้ระดับการแยกแบบ SERIALIZABLE

•ค่าติดต่อกันหลังจากรีสตาร์ทเซิร์ฟเวอร์

•การใช้ค่าซ้ำ

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

https://msdn.microsoft.com/en-us/library/ms186775(v=sql.105).aspx
https://msdn.microsoft.com/en-us/library/ms186775(v=sql.110) ขอบ


ฉันคิดว่าช่องว่างไม่ใช่ปัญหาหลักของฉัน - ปัญหาหลักของฉันคือการมองเห็นค่านิยม (นั่นคือบอกว่าค่าตัวตน 7 จะต้องไม่สามารถสังเกตได้กับแบบสอบถามที่สั่งซื้อโดยค่านั้นก่อนที่ค่าตัวตน 6 คือ.)
เฟเบียน Schmied

1
ฉันได้เห็นค่าตัวตนที่กระทำเช่น: 1, 2, 5, 3, 4
stacylaray

แน่นอนว่าสิ่งนี้สามารถทำซ้ำได้อย่างง่ายดายเช่นใช้สถานการณ์จากคำตอบของ Lennart คำถามที่ฉันกำลังดิ้นรนคือฉันสามารถสังเกตเห็นว่ากระทำการสั่งซื้อเมื่อใช้แบบสอบถามที่มีORDER BY CheckpointSequenceข้อ (ซึ่งเกิดขึ้นเป็นคำสั่งของดัชนีคลัสเตอร์) ฉันคิดว่ามันทำให้เกิดคำถามว่าการสร้างค่า Identity นั้นเชื่อมโยงกับการล็อกที่ใช้โดยคำสั่ง INSERT หรือไม่หรือถ้าสิ่งเหล่านี้เป็นการกระทำที่ไม่เกี่ยวข้องสองอย่างที่ดำเนินการโดย SQL Server อย่างใดอย่างหนึ่ง
เฟเบียน Schmied

1
แบบสอบถามคืออะไร? หากใช้การยืนยันการอ่านในตัวอย่างของคุณการเรียงลำดับตามจะแสดง 1, 2, 3, 5 เนื่องจากมีการยืนยันแล้วและ 4 ไม่ได้เช่นอ่านสกปรก นอกจากนี้คำอธิบายของคุณเกี่ยวกับ NEventStore ระบุว่า "ดังนั้นจึงเป็นสิ่งสำคัญที่เหตุการณ์จะไม่สามารถข้ามได้
stacylaray

แบบสอบถามจะได้รับดังกล่าวข้างต้น ( gist.github.com/fschmied/47f716c32cb64b852f90 ) - มันเพจ SELECT ... FROM Commits WHERE CheckpointSequence > ... ORDER BY CheckpointSequenceแต่เดือดลงไปง่ายๆ ฉันไม่คิดว่าแบบสอบถามนี้จะอ่านผ่านแถวที่ถูกล็อค 4 หรือจะเป็นอย่างไร (ในการทดลองของฉันมันบล็อกเมื่อแบบสอบถามพยายามที่จะได้รับกุญแจล็อคสำหรับแถว 4)
เฟเบียน Schmied

1

ฉันสงสัยว่าบางครั้งอาจนำไปสู่ปัญหาปัญหาที่แย่ลงเมื่อเซิร์ฟเวอร์มีภาระมาก พิจารณาธุรกรรมสองรายการ:

  1. T1: ใส่ลงใน T ... - พูดว่า 5 ใส่แล้ว
  2. T2: ใส่ลงใน T ... - พูดว่า 6 ใส่แล้ว
  3. T2: กระทำ
  4. Reader เห็น 6 แต่ไม่ใช่ 5
  5. T1: ส่งมอบ

ในสถานการณ์ข้างต้น LAST_READ_ID ของคุณจะเป็น 6 ดังนั้น 5 จะไม่ถูกอ่าน


การทดสอบของฉันดูเหมือนจะบ่งชี้ว่าสถานการณ์นี้ไม่เป็นปัญหาเนื่องจาก Reader (ขั้นตอนที่ 4) จะบล็อก (จนกว่า T1 จะปล่อยการล็อก) เมื่อพยายามอ่านแถวที่มีค่า 5 ฉันหายไปบางอย่างหรือไม่
เฟเบียน Schmied

คุณอาจพูดถูกฉันไม่รู้กลไกการล็อคในเซิร์ฟเวอร์ SQL ที่ดี (ดังนั้นฉันสงสัยในคำตอบของฉัน)
Lennart

ขึ้นอยู่กับระดับการแยกของผู้อ่าน ฉันเห็นทั้งบล็อกหรือดูเพียง 6
Michael Green

0

ใช้งานสคริปต์นี้:

BEGIN TRAN;
INSERT INTO dbo.Example DEFAULT VALUES;
COMMIT;

ด้านล่างนี้เป็นล็อคที่ฉันเห็นที่ได้มาและได้รับการปล่อยตัวเมื่อถูกจับโดยเซสชันการขยายกิจกรรม:

name            timestamp                   associated_object_id    mode    object_id   resource_type   session_id  resource_description
lock_acquired   2016-03-29 06:37:28.9968693 1585440722              IX      1585440722  OBJECT          51          
lock_acquired   2016-03-29 06:37:28.9969268 7205759890195415040     IX      0           PAGE            51          1:1235
lock_acquired   2016-03-29 06:37:28.9969306 7205759890195415040     RI_NL   0           KEY             51          (ffffffffffff)
lock_acquired   2016-03-29 06:37:28.9969330 7205759890195415040     X       0           KEY             51          (29cf3326f583)
lock_released   2016-03-29 06:37:28.9969579 7205759890195415040     X       0           KEY             51          (29cf3326f583)
lock_released   2016-03-29 06:37:28.9969598 7205759890195415040     IX      0           PAGE            51          1:1235
lock_released   2016-03-29 06:37:28.9969607 1585440722              IX      1585440722  OBJECT          51      

บันทึกการล็อค RI_N KEY ที่ได้รับทันทีก่อนการล็อคปุ่ม X สำหรับแถวใหม่ที่กำลังสร้าง การล็อคช่วงช่วงเวลาสั้น ๆ นี้จะป้องกันการแทรกแบบพร้อมกันไม่ให้ได้รับการล็อค RI_N KEY อีกอันเนื่องจากการล็อค RI_N นั้นไม่เข้ากัน หน้าต่างที่คุณกล่าวถึงระหว่างขั้นตอนที่ 2 และ 3 ไม่ใช่ข้อกังวลเนื่องจากจะได้รับการล็อคช่วงก่อนการล็อคแถวของคีย์ที่สร้างขึ้นใหม่

ตราบใดที่คุณSELECT...ORDER BYเริ่มการสแกนก่อนที่จะแทรกแถวใหม่ที่ต้องการฉันคาดว่าพฤติกรรมที่คุณต้องการในREAD COMMITTEDระดับการแยกเริ่มต้นตราบใดที่READ_COMMITTED_SNAPSHOTตัวเลือกฐานข้อมูลถูกปิด


1
ตามtechnet.microsoft.com/en-us/library/...สองล็อคด้วยRangeI_Nจะเข้ากันได้คือไม่ปิดกั้นแต่ละอื่น ๆ (ล็อคเป็นส่วนใหญ่มีสำหรับการปิดกั้นในการอ่าน serializable ที่มีอยู่)
เฟเบียน Schmied

@FabianSchmied น่าสนใจ ความขัดแย้งหัวข้อกับเมทริกซ์ความเข้ากันได้ล็อคในtechnet.microsoft.com/en-us/library/ms186396(v=sql.105).aspxซึ่งแสดงให้เห็นว่าล็อคเข้ากันไม่ได้ ตัวอย่างการแทรกในลิงก์ที่คุณกล่าวถึงมีลักษณะการทำงานแบบเดียวกับที่แสดงในการติดตามในคำตอบของฉัน (การล็อคช่วงการแทรกช่วงสั้นเพื่อทดสอบช่วงก่อนการล็อคปุ่มแบบเอกสิทธิ์เฉพาะบุคคล)
Dan Guzman

1
ที่จริงเมทริกซ์กล่าวว่า "N" สำหรับ "ความขัดแย้ง" (ไม่ได้สำหรับ "เข้ากันไม่ได้") :)
Fabian Schmied

0

จากความเข้าใจของฉันใน SQL Server พฤติกรรมเริ่มต้นสำหรับแบบสอบถามที่สองที่จะไม่แสดงผลลัพธ์ใด ๆ จนกว่าแบบสอบถามแรกจะได้รับ หากการสืบค้นแรกทำ ROLLBACK แทนที่จะเป็น COMMIT คุณจะมี ID ที่ขาดหายไปในคอลัมน์ของคุณ

การกำหนดค่าพื้นฐาน

ตารางฐานข้อมูล

ฉันสร้างตารางฐานข้อมูลด้วยโครงสร้างต่อไปนี้:

CREATE TABLE identity_rc_test (
    ID4VALUE INT IDENTITY (1,1), 
    TEXTVALUE NVARCHAR(20),
    CONSTRAINT PK_ID4_VALUE_CLUSTERED 
        PRIMARY KEY CLUSTERED (ID4VALUE, TEXTVALUE)
)

ระดับการแยกฐานข้อมูล

ฉันตรวจสอบระดับการแยกของฐานข้อมูลของฉันด้วยคำสั่งต่อไปนี้:

SELECT snapshot_isolation_state, 
       snapshot_isolation_state_desc, 
       is_read_committed_snapshot_on
FROM sys.databases WHERE NAME = 'mydatabase'

ซึ่งส่งคืนผลลัพธ์ต่อไปนี้สำหรับฐานข้อมูลของฉัน:

snapshot_isolation_state    snapshot_isolation_state_desc   is_read_committed_snapshot_on
0                           OFF                             0

(นี่เป็นการตั้งค่าเริ่มต้นสำหรับฐานข้อมูลใน SQL Server 2012)

สคริปต์ทดสอบ

สคริปต์ต่อไปนี้ถูกดำเนินการโดยใช้การตั้งค่าไคลเอนต์ SQL Server SSMS มาตรฐานและการตั้งค่า SQL Server มาตรฐาน

การตั้งค่าการเชื่อมต่อไคลเอนต์

ไคลเอ็นต์ได้รับการตั้งค่าให้ใช้ระดับการแยกธุรกรรมREAD COMMITTEDตามตัวเลือกแบบสอบถามใน SSMS

แบบสอบถาม 1

แบบสอบถามต่อไปนี้ถูกดำเนินการในหน้าต่างแบบสอบถามที่มี SPID 57

SELECT * FROM dbo.identity_rc_test
BEGIN TRANSACTION [FIRST_QUERY]
INSERT INTO dbo.identity_rc_test (TEXTVALUE) VALUES ('Nine')
/* Commit is commented out to prevent the INSERT from being commited
--COMMIT TRANSACTION [FIRST_QUERY]
--ROLLBACK TRANSACTION [FIRST_QUERY]
*/

แบบสอบถาม 2

แบบสอบถามต่อไปนี้ถูกดำเนินการในหน้าต่างแบบสอบถามด้วย SPID 58

BEGIN TRANSACTION [SECOND_QUERY]
INSERT INTO dbo.identity_rc_test (TEXTVALUE) VALUES ('Ten')
COMMIT TRANSACTION [SECOND_QUERY]
SELECT * FROM dbo.identity_rc_test

แบบสอบถามไม่สมบูรณ์และกำลังรอการปลดล็อค eXclusive ใน PAGE

สคริปต์เพื่อกำหนดการล็อค

สคริปต์นี้แสดงการล็อกที่เกิดขึ้นบนวัตถุฐานข้อมูลสำหรับธุรกรรมสองรายการ:

SELECT request_session_id, resource_type,
       resource_description, 
       resource_associated_entity_id,
       request_mode, request_status
FROM sys.dm_tran_locks
WHERE request_session_id IN (57, 58)

และนี่คือผลลัพธ์:

58  DATABASE                    0                   S   GRANT
57  DATABASE                    0                   S   GRANT
58  PAGE            1:79        72057594040549300   IS  GRANT
57  PAGE            1:79        72057594040549300   IX  GRANT
57  KEY         (a0aba7857f1b)  72057594040549300   X   GRANT
58  KEY         (a0aba7857f1b)  72057594040549300   S   WAIT
58  OBJECT                      245575913           IS  GRANT
57  OBJECT                      245575913           IX  GRANT

ผลลัพธ์แสดงให้เห็นว่าหน้าต่างแบบสอบถามหนึ่ง (SPID 57) มีการล็อคแบบแบ่งใช้ (S) บนฐานข้อมูลล็อคแบบ Intended eXlusive (IX) บน OBJECT, การล็อคแบบ eXlusive (IX) แบบตั้งใจบนหน้าที่ต้องการแทรกและ eXclusive lock (X) บน KEY มันมีการแทรก แต่ยังไม่ได้มุ่งมั่น

เนื่องจากข้อมูลที่ยังไม่ได้รับการสืบค้นแบบสอบถามที่สอง (SPID 58) มีการล็อกแบบแบ่งใช้ (S) ในระดับฐานข้อมูลการล็อกการแบ่งปันที่ตั้งใจไว้ (IS) บน OBJECT การล็อกที่ใช้ร่วมกันแบบตั้งใจ (IS) บนเพจที่ใช้ร่วมกัน ) ล็อคกุญแจด้วยสถานะคำขอ WAIT

สรุป

แบบสอบถามในหน้าต่างแบบสอบถามแรกดำเนินการโดยไม่ต้องยอมรับ เนื่องจากคิวรีที่สองสามารถREAD COMMITTEDข้อมูลได้เท่านั้นจะรอจนกว่าจะเกิดการหมดเวลาหรือจนกว่าจะมีการทำธุรกรรมในคิวรีแรก

นี่คือจากฉันเข้าใจพฤติกรรมเริ่มต้นของ Microsoft SQL Server

คุณควรสังเกตว่า ID นั้นอยู่ในลำดับที่แน่นอนสำหรับการอ่านในภายหลังโดยคำสั่ง SELECT หากคำสั่งแรก COMMITs

หากคำสั่งแรกทำการย้อนกลับจากนั้นคุณจะพบ ID ที่ขาดหายไปในลำดับ แต่ยังคงอยู่กับ ID ในลำดับจากน้อยไปหามาก (หากคุณสร้าง INDEX ด้วยค่าเริ่มต้นหรือตัวเลือก ASC ในคอลัมน์ ID)

ปรับปรุง:

(โผงผาง) ใช่คุณสามารถพึ่งพาคอลัมน์ข้อมูลประจำตัวทำงานได้อย่างถูกต้องจนกว่าคุณจะพบปัญหา มีเพียงหนึ่งHOTFIX เกี่ยวกับ SQL Server 2000 และคอลัมน์ข้อมูลประจำตัวบนเว็บไซต์ของ Microsoft

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

หากคุณมีสัญญาการสนับสนุนของ Microsoft คุณสามารถเปิดกรณีการให้คำปรึกษาและสอบถามข้อมูลเพิ่มเติมได้เสมอ


1
ขอบคุณสำหรับการวิเคราะห์ แต่คำถามของฉันคือถ้ามีช่วงเวลาระหว่างการสร้างIdentityค่าถัดไปและการได้มาของการล็อค KEY บนแถว (ซึ่งการอ่าน / เขียนพร้อมกันอาจตก) ฉันไม่คิดว่าสิ่งนี้จะพิสูจน์ได้ว่าเป็นไปไม่ได้โดยการสังเกตของคุณเนื่องจากไม่มีใครสามารถหยุดการประมวลผลแบบสอบถามและวิเคราะห์การล็อกในช่วงเวลาสั้น ๆ
เฟเบียน Schmied

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

1
ตามปกติการสังเกตของคุณตรงกับของฉัน (และความคาดหวังของฉัน) - ที่ดีที่จะรู้ ฉันสงสัยว่ามีสถานการณ์พิเศษที่พวกเขาจะไม่ถือแม้ว่า
Fabian Schmied
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.