สิ่งที่คุณต้องการคือเลือก ... สำหรับการอัพเดทจากภายในบริบทของการทำธุรกรรม SELECT FOR UPDATE จะทำการล็อคแบบเอกสิทธิ์เฉพาะบุคคลในแถวที่เลือกไว้ราวกับว่าคุณกำลังดำเนินการ UPDATE นอกจากนี้ยังมีการทำงานในระดับการแยกแบบ READ COMMITTED โดยปริยาย เพิ่งทราบว่า SELECT ... for UPDATE นั้นแย่มากสำหรับการเกิดพร้อมกันและควรใช้เมื่อจำเป็นจริงๆเท่านั้น นอกจากนี้ยังมีแนวโน้มที่จะทวีคูณใน codebase เมื่อผู้คนตัดและวาง
นี่คือตัวอย่างเซสชันจากฐานข้อมูล Sakila ซึ่งแสดงให้เห็นถึงพฤติกรรมบางอย่างของคำสั่ง FOR UPDATE
ก่อนอื่นเราก็เลยต้องชัดเจนตั้งระดับการแยกธุรกรรมเป็น REPEATABLE READ โดยทั่วไปไม่จำเป็นเนื่องจากเป็นระดับการแยกเริ่มต้นสำหรับ InnoDB:
session1> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
session1> BEGIN;
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
ในเซสชันอื่นอัปเดตแถวนี้ ลินดาแต่งงานแล้วเปลี่ยนชื่อ:
session2> UPDATE customer SET last_name = 'BROWN' WHERE customer_id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
ย้อนกลับไปในเซสชั่น 1 เพราะเราอยู่ใน REPEATABLE READ ลินดายังคงเป็นวิลเลียมส์ LINDA:
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
แต่ตอนนี้เราต้องการสิทธิพิเศษในการเข้าถึงแถวนี้ดังนั้นเราจึงขอให้มีการอัปเดตแถว โปรดสังเกตว่าขณะนี้เราได้รับแถวย้อนหลังล่าสุดซึ่งได้รับการปรับปรุงในเซสชัน 2 นอกธุรกรรมนี้ นั่นไม่ใช่ REPEATABLE READ นั่นคือ READ COMMITTED
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3 FOR UPDATE;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | BROWN |
+------------+-----------+
1 row in set (0.00 sec)
ลองทดสอบชุดล็อคใน session1 โปรดทราบว่า session2 ไม่สามารถอัปเดตแถว
session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
แต่เรายังคงสามารถเลือกได้
session2> SELECT c.customer_id, c.first_name, c.last_name, a.address_id, a.address FROM customer c JOIN address a USING (address_id) WHERE c.customer_id = 3;
+-------------+------------+-----------+------------+-------------------+
| customer_id | first_name | last_name | address_id | address |
+-------------+------------+-----------+------------+-------------------+
| 3 | LINDA | BROWN | 7 | 692 Joliet Street |
+-------------+------------+-----------+------------+-------------------+
1 row in set (0.00 sec)
และเรายังสามารถอัพเดทตารางลูกที่มีความสัมพันธ์กับคีย์ต่างประเทศ
session2> UPDATE address SET address = '5 Main Street' WHERE address_id = 7;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
session1> COMMIT;
ผลข้างเคียงอีกอย่างหนึ่งคือคุณเพิ่มโอกาสในการก่อให้เกิดการหยุดชะงักอย่างมาก
ในกรณีเฉพาะของคุณคุณอาจต้องการ:
BEGIN;
SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR UPDATE;
-- do some other stuff
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>;
COMMIT;
หากชิ้นส่วน "ทำอะไรบางอย่าง" นั้นไม่จำเป็นและคุณไม่จำเป็นต้องเก็บข้อมูลเกี่ยวกับแถวนั้น ๆ SELECT FOR UPDATE นั้นไม่จำเป็นและสิ้นเปลืองและคุณสามารถทำการอัปเดตแทน:
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;
หวังว่ามันจะสมเหตุสมผล
items
WHEREstatus
= 'รอดำเนินการ' LIMIT 1 สำหรับการอัพเดท" และพวกเขาทั้งสองเห็นแถวเดียวกันจากนั้นหนึ่งคนจะล็อคอีกคน ฉันหวังว่าอย่างใดมันจะสามารถบายพาสแถวล็อคและกลับไปรายการถัดไปซึ่งอยู่ระหว่าง ..