ฉันจะ จำกัด ขั้นตอนการจัดเก็บ SQL ที่จะเรียกใช้ครั้งละหนึ่งคนได้อย่างไร


12

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

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

สิ่งที่ฉันต้องการสำหรับบุคคลอื่นที่พยายามเริ่มต้นเพื่อรับข้อผิดพลาดในขณะที่ฉันกำลังเรียกใช้กระบวนการ

ฉันได้ลองใช้ sp_getapplock แล้ว แต่ฉันไม่สามารถจัดการเพื่อห้ามไม่ให้บุคคลนั้นเรียกใช้โพรซีเดอร์ได้

ฉันพยายามค้นหาโพรซีเดอร์ด้วย sys.dm_exec_requests และบล็อกโพรซีเดอร์ในขณะที่มันใช้งานได้ฉันคิดว่ามันไม่เหมาะสมเพราะในบางเซิร์ฟเวอร์ฉันไม่ได้รับอนุญาตให้เรียกใช้ sys.dm_exec_sql_text (sql_handle)

วิธีที่ดีที่สุดสำหรับฉันที่จะทำคืออะไร?


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

คำตอบ:


15

หากต้องการเพิ่มคำตอบของ @ Tibor-Karaszi การตั้งค่าการหมดเวลาการล็อคไม่ได้เกิดข้อผิดพลาดจริง ๆ (ฉันได้ส่งการประชาสัมพันธ์กับเอกสาร) sp_getapplock เพิ่งส่งกลับ -1 ดังนั้นคุณต้องตรวจสอบค่าตอบแทน เช่นนี้

create or alter procedure there_can_be_only_one 
as
begin
begin transaction

  declare @rv int
  exec @rv = sp_getapplock 'only_one','exclusive','Transaction',0
  if @rv < 0
   begin
      throw 50001, 'There is already an instance of this procedure running.', 10
   end

  --do stuff
  waitfor delay '00:00:20'


commit transaction
end

8

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


7

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

CREATE TABLE dbo.ProcedureLock
    (
    ProcedureLockID INT NOT NULL IDENTITY(1,1)
    , ProcedureName SYSNAME NOT NULL
    , IsLocked BIT NOT NULL CONSTRAINT DF_ProcedureLock_IsLocked DEFAULT (0)
    , UserSPID INT NULL
    , DateLockTaken DATETIME2(7) NULL
    , DateLockExpires DATETIME2(7) NULL
    , CONSTRAINT PK_ProcedureLock PRIMARY KEY CLUSTERED (ProcedureLockID)
    )

CREATE UNIQUE NONCLUSTERED INDEX IDXUQ_ProcedureLock_ProcedureName
    ON dbo.ProcedureLock (ProcedureName)

INSERT INTO dbo.ProcedureLock
    (ProcedureName, IsLocked)
VALUES ('dbo.DoSomeWork', 0)

GO

CREATE PROCEDURE dbo.DoSomeWork
AS
BEGIN

    /** Take Lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 1
        , UserSPID = @@SPID
        , DateLockTaken = SYSDATETIME()
        , DateLockExpires = DATEADD(MINUTE, 10, SYSDATETIME())
    WHERE ProcedureName = 'dbo.DoSomeWork'
        AND (IsLocked = 0
            OR (IsLocked = 1 AND DateLockExpires < SYSDATETIME())
            )

    IF COALESCE(@@ROWCOUNT, 0) = 0
    BEGIN
        ;THROW 50000, 'This procedure can only be run one at a time, please wait', 1;
    END

    /** DO WHATEVER NEEDS TO BE DONE */

    /** Release the lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 0
        , UserSPID = NULL
        , DateLockTaken = NULL
        , DateLockExpires = NULL
    WHERE ProcedureName = 'dbo.DoSomeWork'

END

1
สิ่งนี้คล้ายกับ (หรืออาจเหมือนกับ) สิ่งที่ฉันคิดทันทีหลังจากอ่านคำถาม แต่ฉันมีปัญหากับความคิดที่ว่าฉันไม่แน่ใจทั้งหมดวิธีที่อยู่และไม่เห็นมันตอบในคำตอบของคุณ ทั้ง. ความกังวลของฉันคืออะไรถ้ามีบางสิ่งเกิดขึ้นระหว่างส่วน "ทำอะไรที่ต้องทำ" คุณจะรีเซ็ตIsLockedสถานะเป็น 0 ในกรณีนั้นได้อย่างไร ฉันอยากรู้เกี่ยวกับการใช้ของคุณCOALESCEที่นี่ สามารถ@@ROWCOUNTเป็นโมฆะหลังจากที่คำสั่งเป็นUPDATEอย่างไร ในที่สุดก็แค่ nitpick เล็กน้อยทำไมต้องใส่เครื่องหมายอัฒภาคไว้ข้างหน้าTHROWคำพูดในกรณีเฉพาะนั้น
Andriy M

การหมดอายุการล็อคเป็นวิธีหนึ่งในการจัดการ มันจะต้องถูกตั้งค่าเป็นกรอบเวลาที่สมเหตุสมผลฉันได้ตั้งค่าเป็น 10 นาทีในตัวอย่างของฉัน คุณสามารถแค็ปซูลตรรกะการทำงานของคุณในลอง / จับบล็อกและปลดล็อคในการจับถ้าคุณต้องการเช่นกัน ฉันใช้ COALESCE ไม่ติดนิสัย แต่ไม่มี @@ ROWCOUNT ไม่สามารถเป็น NULL ได้ เซมิโคลอนชั้นนำมาจากการทำงานกับโครงการฐานข้อมูล Visual Studio มันบ่นถ้ามันไม่มี ไม่มีอันตรายใด ๆ
Jonathan Fite

-1

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

เพื่อป้องกันความไม่สอดคล้องของฐานข้อมูลชนิดต่าง ๆ มาตรฐาน SQL มีระดับการแยกธุรกรรมสี่ระดับ:

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

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

  • READ COMMITTEDซึ่งเหมือนกันในการล็อคฐานข้อมูล
  • SNAPSHOT ISOLATIONที่ฐานข้อมูลเห็นภาพรวมของข้อมูลทั้งหมดและหากพยายามที่จะอัพเดทแถวที่ได้รับการปรับปรุงโดยการทำธุรกรรมอื่น ๆ ก็จะถูกยกเลิก แต่ยังมีความผิดปกติบางอย่างที่รู้จักกันดีที่สามารถเกิดขึ้นได้
  • SERIALIZABLEซึ่งเหมือนกับในการล็อคฐานข้อมูล แต่คราวนี้นำไปใช้ในลักษณะที่แตกต่างไม่ใช่โดยการล็อค แต่โดยมั่นใจว่าไม่มีการละเมิดลำดับและหากตรวจพบการละเมิดดังกล่าวยกเลิกการทำธุรกรรม

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

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

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

ตอนนี้ SQL Server สนับสนุนระดับการแยกSERIALIZABLEอย่างแท้จริง(แทนที่จะเป็นสแนปชอตการแยกสแนปชอตที่อยู่เบื้องหลังคำหลักSERIALIZABLE ) หรือไม่ ค่อนข้างตรงไปตรงมาฉันไม่รู้: ฐานข้อมูลเดียวที่ฉันรู้ว่ารองรับคือ PostgreSQL

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


ฉันใช้มัน downvote หมายถึงใครบางคนที่ตรวจสอบว่า SQL Server มีระดับการแยกอย่างต่อเนื่องและพบว่ามันไม่
juhist

-2

ฉันตระหนักว่าปัญหา 'ของจริง' อาจซับซ้อนกว่านี้

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

หวังว่าจะช่วยได้
-Chris C.


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

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

@underscore_d ใช่มันอาจมีค่าใช้จ่ายสูงเกินไปหรือไม่จำเป็นเสมอไป the 'real' problem may be more complexนี่คือเหตุผลที่ผมเริ่มคำตอบของฉันมีคำสั่งว่า ในกรณีที่ไม่มีตัวกระตุ้นจะเป็นคำตอบที่ดี รวมทั้งอาจจะง่ายต่อการทดสอบและบำรุงรักษาเพราะมันเป็นคุณสมบัติของฐานข้อมูลแทนที่จะเป็นโซลูชันที่กำหนดเอง
เจ. คริสคอมป์ตัน

@AndriyM ฉันลบคำนั้นออกทันทีแทนที่ด้วยทริกเกอร์ ref เพื่อแทรก / อัปเดต ขอโทษสำหรับความสับสน.
เจคริสคอมป์ตัน

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