ไม่มีการเข้าถึงเพื่อเขียนพร้อมกัน
เป็นตัวเป็นตนตัวเลือกในCTEและเข้าร่วมกับมันในประโยคของFROM
UPDATE
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()
คู่มือ:
เมื่อได้รับที่ระดับเซสชันล็อคคำแนะนำจะถูกเก็บไว้จนกว่าจะมีการเปิดตัวอย่างชัดเจนหรือสิ้นสุดเซสชัน
ที่เกี่ยวข้อง: