เลื่อนระดับอัปเดต… จำกัด 1


77

ฉันมีฐานข้อมูล Postgres ซึ่งมีรายละเอียดเกี่ยวกับกลุ่มของเซิร์ฟเวอร์เช่นสถานะเซิร์ฟเวอร์ ('ใช้งาน', 'สแตนด์บาย' ฯลฯ ) เซิร์ฟเวอร์ที่ใช้งานอยู่ตลอดเวลาอาจจำเป็นต้องล้มเหลวในการสแตนด์บายและฉันไม่สนใจว่าจะใช้สแตนด์บายใดเป็นพิเศษ

ฉันต้องการคิวรีฐานข้อมูลเพื่อเปลี่ยนสถานะของสแตนด์บาย - เพียงแค่หนึ่ง - และส่งคืน IP ของเซิร์ฟเวอร์ที่จะใช้ การเลือกสามารถโดยพลการ: เนื่องจากสถานะของเซิร์ฟเวอร์เปลี่ยนแปลงด้วยแบบสอบถามจึงไม่สำคัญว่าจะเลือกสแตนด์บายใด

เป็นไปได้หรือไม่ที่จะ จำกัด คิวรีของฉันให้อัปเดตเดียว

นี่คือสิ่งที่ฉันมี:

UPDATE server_info SET status = 'active' 
WHERE status = 'standby' [[LIMIT 1???]] 
RETURNING server_ip;

Postgres ไม่ชอบสิ่งนี้ ฉันจะทำอะไรที่แตกต่างกัน


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

@eckes นั่นเป็นแนวคิดที่น่าสนใจ ในกรณีของฉัน "เลือกเซิร์ฟเวอร์ในรหัส" จะหมายถึงก่อนอ่านรายการเซิร์ฟเวอร์ที่มีอยู่จาก db แล้วอัปเดตระเบียน เนื่องจากแอปพลิเคชันจำนวนมากสามารถดำเนินการนี้ได้จึงมีเงื่อนไขการแข่งขันและจำเป็นต้องมีการทำงานของอะตอม (หรือเมื่อ 5 ปีก่อน) การเลือกไม่จำเป็นต้องกำหนดไว้ล่วงหน้า
vastlysuperiorman

คำตอบ:


125

ไม่มีการเข้าถึงเพื่อเขียนพร้อมกัน

เป็นตัวเป็นตนตัวเลือกในCTEและเข้าร่วมกับมันในประโยคของFROMUPDATE

WITH cte AS (
   SELECT server_ip          -- pk column or any (set of) unique column(s)
   FROM   server_info
   WHERE  status = 'standby'
   LIMIT  1                  -- arbitrary pick (cheapest)
   )
UPDATE server_info s
SET    status = 'active' 
FROM   cte
WHERE  s.server_ip = cte.server_ip
RETURNING server_ip;

ฉันเคยมีแบบสอบถามย่อยแบบธรรมดาที่นี่ แต่นั่นสามารถเลี่ยงการLIMITสอบถามแผนบางอย่างตามที่Feikeชี้:

ผู้วางแผนอาจเลือกที่จะสร้างแผนที่ดำเนินการลูปที่ซ้อนกันเหนือLIMITingเคียวรีย่อยทำให้เกิดUPDATEsมากกว่าLIMITเช่น:

 Update on buganalysis [...] rows=5
   ->  Nested Loop
         ->  Seq Scan on buganalysis
         ->  Subquery Scan on sub [...] loops=11
               ->  Limit [...] rows=2
                     ->  LockRows
                           ->  Sort
                                 ->  Seq Scan on buganalysis

ทำซ้ำกรณีทดสอบ

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

หรือใช้ต่ำต้อยความสัมพันธ์แบบสอบถามย่อยLIMIT 1สำหรับกรณีที่เรียบง่ายด้วย เรียบง่ายเร็วขึ้น:

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         )
RETURNING server_ip;

ด้วยการเข้าถึงการเขียนพร้อมกัน

สมมติว่าระดับการแยกเริ่มต้นREAD COMMITTEDสำหรับทั้งหมดนี้ ระดับการแยกที่เข้มงวด ( REPEATABLE READและSERIALIZABLE) อาจยังคงส่งผลให้เกิดข้อผิดพลาดในการทำให้เป็นอนุกรม ดู:

ภายใต้โหลดการเขียนพร้อมกันเพิ่มFOR UPDATE SKIP LOCKEDเพื่อล็อคแถวเพื่อหลีกเลี่ยงสภาพการแข่งขัน SKIP LOCKEDถูกเพิ่มใน Postgres 9.5สำหรับรุ่นเก่าดูด้านล่าง คู่มือ:

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

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         FOR    UPDATE SKIP LOCKED
         )
RETURNING server_ip;

หากไม่มีแถวที่ผ่านการรับรองและปลดล็อคจะไม่มีอะไรเกิดขึ้นในแบบสอบถามนี้ (ไม่มีการอัปเดตแถว) และคุณจะได้รับผลลัพธ์ที่ว่างเปล่า สำหรับการปฏิบัติที่ไม่สำคัญซึ่งหมายความว่าคุณทำเสร็จแล้ว

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

SELECT NOT EXISTS (
   SELECT 1
   FROM   server_info
   WHERE  status = 'standby'
   );

SELECTยังเห็นแถวที่ถูกล็อค ถ้าไม่ส่งคืนtrueจะมีการดำเนินการอย่างน้อยหนึ่งแถวและการทำธุรกรรมอาจยังคงย้อนกลับได้ (หรือแถวใหม่ได้รับการเพิ่มในขณะเดียวกัน.) รอสักครู่แล้ววงที่สองขั้นตอน ( UPDATEจนกว่าคุณจะไม่ได้รับแถวหลัง; SELECT... ) trueจนกว่าคุณจะได้รับ

ที่เกี่ยวข้อง:

ไม่มีSKIP LOCKEDใน PostgreSQL 9.4 หรือเก่ากว่า

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         FOR    UPDATE
         )
RETURNING server_ip;

ธุรกรรมที่เกิดขึ้นพร้อมกันที่พยายามล็อกแถวเดียวกันจะถูกบล็อกจนกว่ารายการแรกจะปลดล็อก

หากรายการแรกถูกย้อนกลับรายการถัดไปจะล็อคและดำเนินการตามปกติ คนอื่น ๆ ในคิวรอต่อไป

หากกระทำครั้งแรกWHEREเงื่อนไขจะถูกประเมินอีกครั้งและหากไม่มีการเปลี่ยนแปลงTRUEใด ๆ อีกต่อstatusไป CTE (ค่อนข้างแปลกใจ) จะไม่ส่งกลับแถว ไม่มีอะไรเกิดขึ้น. นั่นเป็นพฤติกรรมที่ต้องการเมื่อการทำธุรกรรมทุกคนต้องการที่จะปรับปรุงเดียวกันแถว แต่ไม่เมื่อการทำธุรกรรมแต่ละต้องการที่จะปรับปรุงถัดไปแถว และเนื่องจากเราต้องการอัปเดตแถว (หรือสุ่ม ) โดยพลการจึงไม่มีจุดรอเลย

เราสามารถปลดล็อคสถานการณ์ด้วยความช่วยเหลือของการล็อคคำแนะนำ :

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         AND    pg_try_advisory_xact_lock(id)
         LIMIT  1
         FOR    UPDATE
         )
RETURNING server_ip;

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

idเป็นbigintคอลัมน์ใด ๆ ที่ไม่ซ้ำกัน(หรือประเภทใด ๆ ที่มีลักษณะคล้ายกับint4หรือint2)

หากมีการใช้การล็อกคำแนะนำสำหรับตารางหลายตารางในฐานข้อมูลของคุณพร้อมกันให้ยกเลิกการเชื่อมต่อด้วยpg_try_advisory_xact_lock(tableoid::int, id)- idเป็นค่าเฉพาะintegerที่นี่
เนื่องจากtableoidเป็นbigintปริมาณจึงสามารถไหลล้นในทางทฤษฎีintegerได้ หากคุณหวาดระแวงมากพอให้ใช้(tableoid::bigint % 2147483648)::intแทน - ทิ้ง "hash collision" เชิงทฤษฎีสำหรับการหวาดระแวงอย่างแท้จริง ...

นอกจากนี้ Postgres มีอิสระที่จะทดสอบWHEREเงื่อนไขในลำดับใดก็ได้ มันสามารถทดสอบ pg_try_advisory_xact_lock()และรับการล็อกก่อน status = 'standby'ซึ่งอาจส่งผลให้เกิดการล็อกคำแนะนำเพิ่มเติมในแถวที่ไม่เกี่ยวข้องซึ่งstatus = 'standby'ไม่เป็นความจริง คำถามที่เกี่ยวข้องกับ SO:

โดยทั่วไปคุณสามารถเพิกเฉยสิ่งนี้ได้ เพื่อรับประกันว่าเฉพาะแถวที่มีคุณสมบัติจะถูกล็อคคุณสามารถรังกริยา (s) ใน CTE เช่นด้านบนหรือแบบสอบถามย่อยที่มีการOFFSET 0สับ (ป้องกันไม่ให้อินไลน์) ตัวอย่าง:

หรือ (ถูกกว่าสำหรับการสแกนตามลำดับ) ซ้อนเงื่อนไขในCASEคำสั่งเช่น:

WHERE  CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END

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

เนื่องจากคุณไม่สามารถแน่ใจได้ว่ามีการใช้ดัชนีในการโทรทุกครั้งคุณสามารถ:

WHERE  status = 'standby'
AND    CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END

The CASEซ้ำซ้อนทางตรรกะ แต่มันเซิร์ฟเวอร์วัตถุประสงค์ที่กล่าวถึง

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

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

ที่เกี่ยวข้อง:

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