Kondisi di mana akan dihormati selama situasi balapan, tetapi Anda harus berhati-hati saat memeriksa untuk melihat siapa yang memenangkan balapan.
Pertimbangkan demonstrasi berikut tentang cara kerjanya dan mengapa Anda harus berhati-hati.
Pertama, siapkan beberapa tabel minimal.
CREATE TABLE table1 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY,
`locked` TINYINT UNSIGNED NOT NULL,
`updated_by_connection_id` TINYINT UNSIGNED DEFAULT NULL
) ENGINE = InnoDB;
CREATE TABLE table2 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY
) ENGINE = InnoDB;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
id
memainkan peran id
di tabel Anda, updated_by_connection_id
bertindak seperti assignedPhone
, dan locked
seperti reservationCompleted
.
Sekarang mari kita mulai tes balapan. Anda harus memiliki 2 jendela commandline/terminal terbuka, terhubung ke mysql dan menggunakan database tempat Anda membuat tabel ini.
Sambungan 1
start transaction;
Sambungan 2
start transaction;
Sambungan 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
Sambungan 2
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
Koneksi 2 sedang menunggu
Sambungan 1
SELECT * FROM table1 WHERE id = 1;
commit;
Pada titik ini, koneksi 2 dilepaskan untuk melanjutkan dan menampilkan yang berikut:
Sambungan 2
SELECT * FROM table1 WHERE id = 1;
commit;
Semuanya terlihat baik-baik saja. Kami melihat bahwa ya, klausa WHERE dihormati dalam situasi balapan.
Alasan saya mengatakan Anda harus berhati-hati, karena dalam aplikasi nyata hal-hal tidak selalu sesederhana ini. Anda MUNGKIN memiliki tindakan lain yang terjadi dalam transaksi, dan itu benar-benar dapat mengubah hasil.
Mari kita reset database dengan berikut ini:
delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
Dan sekarang, pertimbangkan situasi ini, di mana SELECT dilakukan sebelum UPDATE.
Sambungan 1
start transaction;
SELECT * FROM table2;
Sambungan 2
start transaction;
SELECT * FROM table2;
Sambungan 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
Sambungan 2
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
Koneksi 2 sedang menunggu
Sambungan 1
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Pada titik ini, koneksi 2 dilepaskan untuk melanjutkan dan menampilkan yang berikut:
Oke, mari kita lihat siapa yang menang:
Sambungan 2
SELECT * FROM table1 WHERE id = 1;
Tunggu apa? Mengapa locked
0 dan updated_by_connection_id
NULL??
Ini adalah kehati-hatian yang saya sebutkan. Pelakunya sebenarnya karena kami melakukan seleksi di awal. Untuk mendapatkan hasil yang benar, kita dapat menjalankan yang berikut:
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Dengan menggunakan SELECT ... FOR UPDATE kita bisa mendapatkan hasil yang tepat. Ini bisa sangat membingungkan (seperti yang terjadi pada saya, awalnya), karena SELECT dan SELECT ... FOR UPDATE memberikan dua hasil yang berbeda.
Alasan hal ini terjadi adalah karena tingkat isolasi default READ-REPEATABLE
. Saat SELECT pertama dilakukan, tepat setelah start transaction;
, snapshot dibuat. Semua pembacaan non-pembaruan di masa mendatang akan dilakukan dari snapshot itu.
Oleh karena itu, jika Anda secara naif PILIH setelah Anda melakukan pembaruan, itu akan menarik informasi dari snapshot asli itu, yaitu sebelum baris telah diperbarui. Dengan melakukan SELECT ... FOR UPDATE Anda memaksanya untuk mendapatkan informasi yang benar.
Namun, sekali lagi, dalam aplikasi nyata ini bisa menjadi masalah. Katakanlah, misalnya, permintaan Anda dibungkus dalam sebuah transaksi, dan setelah melakukan pembaruan, Anda ingin menampilkan beberapa informasi. Mengumpulkan dan mengeluarkan informasi tersebut dapat ditangani dengan kode terpisah yang dapat digunakan kembali, yang TIDAK ingin Anda buang dengan klausa FOR UPDATE "untuk berjaga-jaga." Itu akan menyebabkan banyak frustrasi karena penguncian yang tidak perlu.
Sebaliknya, Anda ingin mengambil jalur yang berbeda. Anda memiliki banyak pilihan di sini.
Satu, adalah memastikan Anda melakukan transaksi setelah UPDATE selesai. Dalam kebanyakan kasus, ini mungkin pilihan terbaik dan paling sederhana.
Pilihan lain adalah tidak mencoba menggunakan SELECT untuk menentukan hasilnya. Sebagai gantinya, Anda mungkin dapat membaca baris yang terpengaruh, dan menggunakannya (1 baris diperbarui vs 0 baris diperbarui) untuk menentukan apakah PEMBARUAN berhasil.
Pilihan lain, dan salah satu yang sering saya gunakan, karena saya ingin menyimpan satu permintaan (seperti permintaan HTTP) sepenuhnya dibungkus dalam satu transaksi, adalah memastikan bahwa pernyataan pertama yang dieksekusi dalam suatu transaksi adalah UPDATE atau PILIH ... UNTUK PEMBARUAN . Itu akan menyebabkan snapshot TIDAK diambil sampai koneksi diizinkan untuk dilanjutkan.
Mari kita setel ulang database pengujian kita dan lihat cara kerjanya.
delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
Sambungan 1
start transaction;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
Sambungan 2
start transaction;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
Koneksi 2 sedang menunggu.
Sambungan 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Koneksi 2 sekarang dirilis.
Sambungan 2
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
| 1 | 1 | 1 |
+----+--------+--------------------------+
Di sini Anda sebenarnya dapat meminta kode sisi server Anda memeriksa hasil SELECT ini dan mengetahui bahwa itu akurat, dan bahkan tidak melanjutkan ke langkah selanjutnya. Tapi, untuk kelengkapan, saya akan menyelesaikan seperti sebelumnya.
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Sekarang Anda dapat melihat bahwa di Connection 2 SELECT dan SELECT ... FOR UPDATE memberikan hasil yang sama. Ini karena snapshot yang dibaca SELECT tidak dibuat sampai setelah Koneksi 1 dilakukan.
Jadi, kembali ke pertanyaan awal Anda:Ya, klausa WHERE diperiksa oleh pernyataan UPDATE, dalam semua kasus. Namun, Anda harus berhati-hati dengan SELECT yang mungkin Anda lakukan, untuk menghindari kesalahan dalam menentukan hasil UPDATE tersebut.
(Ya, opsi lain adalah mengubah tingkat isolasi transaksi. Namun, saya tidak benar-benar memiliki pengalaman dengan itu dan gotchya apa pun yang mungkin ada, jadi saya tidak akan membahasnya.)