จัดการกับการทำงานพร้อมกันของ PostgreSQL โดยใช้ Rails find_or_create

ด้วยเหตุผลบางประการ โค้ดนี้จึงสามารถสร้างเกมที่ซ้ำกันหากผู้ใช้ที่แตกต่างกันเรียกใช้ในเวลาเดียวกัน:

game = Game.find_or_create_by(
    status: Game::STATUS[:waiting],
    category_id: params[:category_id],
    private: 0
) do |g|
  is_new = true
  g.user = current_user
end

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

ถ้าเป็นเช่นนั้น ฉันต้องการวิธีที่ถูกต้องเพื่อหลีกเลี่ยง บางทีฉันควรใช้ธุรกรรม Rails หรือการล็อค Postgres แต่ฉันต้องการตัวอย่างการใช้งานจริงๆ

ขอบคุณ


person Alexander Zinchuk    schedule 22.05.2014    source แหล่งที่มา
comment
ฉันเชื่อว่าปัญหาของคุณอยู่ใน current_user แต่ก่อนอื่นให้เปลี่ยน find_or_create_by เป็น create_by เนื่องจาก is_new เป็นจริง   -  person Hitham S. AlQadheeb    schedule 22.05.2014
comment
เพิ่มและจัดทำดัชนีลงในฐานข้อมูล เพื่อใช้รายการที่ซ้ำกันหรือไม่   -  person manu29.d    schedule 22.05.2014
comment
manu29.d จริงๆ แล้วฉันไม่สามารถใช้ดัชนี UNIQUE ใน [status, category_id, private] ได้: เพราะฉันต้องการเฉพาะบันทึกที่ไม่ซ้ำเมื่อสถานะเป็น 0 (WAITING) สำหรับสถานะอื่นๆ ฉันสามารถมีบันทึกโดยทำซ้ำคอลัมน์เหล่านั้นได้   -  person Alexander Zinchuk    schedule 22.05.2014
comment
ฮิธัม เอส. อัลก็อดฮีบ ฉันไม่เข้าใจสิ่งที่คุณหมายถึง ปัญหาของ current_user คืออะไร และ create_by คืออะไร is_new ใช้ในโค้ดต่อไปนี้ โปรดทราบว่าเพิ่งสร้างเรกคอร์ด   -  person Alexander Zinchuk    schedule 22.05.2014


คำตอบ (1)


สามารถเกิดขึ้นได้ในระดับที่มีการทำงานพร้อมกันสูง

ตามเอกสาร Rails คำค้นหาเหล่านี้จะทำงาน:

SELECT * FROM games WHERE status = 'waiting' AND ... LIMIT 1;
INSERT INTO games (status, ...) VALUES ('waiting', ...);

ครั้งที่สองจะทำงานเท่านั้น เมื่อครั้งแรกไม่ได้ส่งคืนแถว

เป็นไปได้ หากการเชื่อมต่อสองรายการ (หรือมากกว่า) เริ่มต้นการสืบค้นครั้งแรกภายในเวลาไม่กี่ไมโครวินาที กระบวนการหลายรายการนั้นจะสร้างหลายรายการ เพื่อป้องกันไม่ให้เป็นเช่นนั้น คุณสามารถใช้ ACCESS EXCLUSIVE lock บนตารางนั้น หรือใช้ ล็อคที่ปรึกษา ที่กำหนดเอง

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

แก้ไข:

ACCESS EXCLUSIVE สามารถรับการล็อกได้ผ่าน LOCK คำสั่ง

คุณสามารถรับการล็อกคำแนะนำได้โดยใช้ pg_advisory_lock(id) function

ทั้งสองต้องการให้คุณเรียกใช้คำสั่ง SQL โดยพลการ


อีกวิธีหนึ่งคือการใช้ข้อความค้นหาที่กำหนดเองกับ:

  1. แทรกเฉพาะในกรณีที่ไม่มีอยู่ (& กลับมาพร้อมกับทุกช่อง)
  2. เลือกเฉพาะในกรณีที่ไม่ได้แทรก

สิ่งที่ต้องการ:

INSERT INTO games (status, category_id, private)
     SELECT 'waiting', 2, 0
      WHERE NOT EXISTS (
             SELECT 1
               FROM games
              WHERE status = 'waiting'
                AND category_id = 2
                AND private = 0
       )
  RETURNING *;

-- only select, when this not inserted anything

SELECT *
  FROM games
 WHERE status = 'waiting'
   AND category_id = 2
   AND private = 0
person pozs    schedule 22.05.2014
comment
ขอบคุณ คุณช่วยยกตัวอย่างเล็กๆ น้อยๆ เกี่ยวกับวิธีใช้การล็อคประเภทเหล่านั้นในโค้ด Rails ของฉันได้ไหม ธุรกรรม Rails จะไม่มีประโยชน์ในกรณีของฉันใช่ไหม - person Alexander Zinchuk; 22.05.2014