Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Truy vấn cập nhật MySQL - Điều kiện 'ở đâu' có được tôn trọng về điều kiện chủng tộc và khóa hàng không? (php, PDO, MySQL, InnoDB)

Điều kiện sẽ được tôn trọng trong một tình huống đua, nhưng bạn phải cẩn thận khi kiểm tra xem ai đã thắng cuộc đua.

Hãy xem xét minh họa sau đây về cách hoạt động của điều này và tại sao bạn phải cẩn thận.

Đầu tiên, hãy thiết lập một số bảng tối thiểu.

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 đóng vai trò của id trong bảng của bạn, updated_by_connection_id hoạt động giống như assignedPhonelocked như reservationCompleted .

Bây giờ chúng ta hãy bắt đầu kiểm tra cuộc đua. Bạn phải mở 2 cửa sổ dòng lệnh / dòng lệnh, được kết nối với mysql và sử dụng cơ sở dữ liệu nơi bạn đã tạo các bảng này.

Kết nối 1

start transaction;

Kết nối 2

start transaction;

Kết nối 1

UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;

Kết nối 2

UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;

Kết nối 2 hiện đang chờ

Kết nối 1

SELECT * FROM table1 WHERE id = 1;
commit;

Tại thời điểm này, kết nối 2 được giải phóng để tiếp tục và kết quả như sau:

Kết nối 2

SELECT * FROM table1 WHERE id = 1;
commit;

Mọi thứ có vẻ ổn. Chúng tôi thấy rằng có, mệnh đề WHERE được tôn trọng trong tình huống chủng tộc.

Tuy nhiên, lý do tôi nói bạn phải cẩn thận là bởi vì trong một ứng dụng thực tế, mọi thứ không phải lúc nào cũng đơn giản như vậy. Bạn CÓ THỂ có các hành động khác đang diễn ra trong giao dịch và điều đó thực sự có thể thay đổi kết quả.

Hãy đặt lại cơ sở dữ liệu như sau:

delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);

Và bây giờ, hãy xem xét tình huống này, trong đó lệnh CHỌN được thực hiện trước CẬP NHẬT.

Kết nối 1

start transaction;

SELECT * FROM table2;

Kết nối 2

start transaction;

SELECT * FROM table2;

Kết nối 1

UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;

Kết nối 2

UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;

Kết nối 2 hiện đang chờ

Kết nối 1

SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;

Tại thời điểm này, kết nối 2 được giải phóng để tiếp tục và kết quả như sau:

Được rồi, hãy xem ai đã thắng:

Kết nối 2

SELECT * FROM table1 WHERE id = 1;

Chờ đã, cái gì? Tại sao locked 0 và updated_by_connection_id KHÔNG ??

Đây là sự cẩn thận mà tôi đã đề cập. Thủ phạm thực sự là do chúng ta đã chọn ngay từ đầu. Để có kết quả chính xác, chúng tôi có thể chạy như sau:

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;

Bằng cách sử dụng SELECT ... FOR UPDATE, chúng ta có thể nhận được kết quả phù hợp. Điều này có thể rất khó hiểu (như đối với tôi, ban đầu), vì CHỌN và CHỌN ... CHO CẬP NHẬT đang đưa ra hai kết quả khác nhau.

Lý do điều này xảy ra là do mức cô lập mặc định READ-REPEATABLE . Khi lần CHỌN đầu tiên được thực hiện, ngay sau khi start transaction; , một ảnh chụp nhanh được tạo. Tất cả các lần đọc không cập nhật trong tương lai sẽ được thực hiện từ ảnh chụp nhanh đó.

Do đó, nếu bạn chỉ CHỌN một cách ngây thơ sau khi cập nhật, nó sẽ lấy thông tin từ ảnh chụp nhanh ban đầu đó trước đó hàng đã được cập nhật. Bằng cách thực hiện CHỌN ... ĐỂ CẬP NHẬT, bạn buộc nó phải nhận được thông tin chính xác.

Tuy nhiên, một lần nữa, trong một ứng dụng thực, điều này có thể là một vấn đề. Ví dụ:giả sử yêu cầu của bạn được bao bọc trong một giao dịch và sau khi thực hiện cập nhật, bạn muốn xuất một số thông tin. Việc thu thập và xuất thông tin đó có thể được xử lý bằng mã riêng biệt, có thể tái sử dụng, mà bạn KHÔNG muốn sử dụng các mệnh đề FOR ​​UPDATE "để đề phòng". Điều đó sẽ dẫn đến nhiều sự thất vọng do khóa không cần thiết.

Thay vào đó, bạn sẽ muốn đi một con đường khác. Bạn có nhiều lựa chọn ở đây.

Một là đảm bảo bạn thực hiện giao dịch sau khi CẬP NHẬT hoàn tất. Trong hầu hết các trường hợp, đây có lẽ là lựa chọn tốt nhất, đơn giản nhất.

Một tùy chọn khác là không thử sử dụng SELECT để xác định kết quả. Thay vào đó, bạn có thể đọc các hàng bị ảnh hưởng và sử dụng thông tin đó (cập nhật 1 hàng so với cập nhật 0 hàng) để xác định xem CẬP NHẬT có thành công hay không.

Một tùy chọn khác và một tùy chọn mà tôi sử dụng thường xuyên, vì tôi muốn giữ một yêu cầu duy nhất (như một yêu cầu HTTP) được gói trọn trong một giao dịch duy nhất, là đảm bảo rằng câu lệnh đầu tiên được thực hiện trong một giao dịch là CẬP NHẬT hoặc CHỌN ... ĐỂ CẬP NHẬT . Điều đó sẽ khiến ảnh chụp nhanh KHÔNG được chụp cho đến khi kết nối được phép tiếp tục.

Hãy đặt lại cơ sở dữ liệu thử nghiệm của chúng tôi một lần nữa và xem cách này hoạt động như thế nào.

delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);

Kết nối 1

start transaction;

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;

Kết nối 2

start transaction;

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;

Kết nối 2 hiện đang chờ.

Kết nối 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;

Kết nối 2 hiện đã được phát hành.

Kết nối 2

+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+

Tại đây, bạn thực sự có thể yêu cầu mã phía máy chủ của mình kiểm tra kết quả của CHỌN này và biết nó là chính xác và thậm chí không cần tiếp tục với các bước tiếp theo. Nhưng, để hoàn thiện, tôi sẽ hoàn thành như trước.

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;

Bây giờ bạn có thể thấy rằng trong Kết nối 2, CHỌN và CHỌN ... CHO CẬP NHẬT cho cùng một kết quả. Điều này là do ảnh chụp nhanh mà SELECT đọc từ đó không được tạo cho đến sau khi Kết nối 1 đã được cam kết.

Vì vậy, quay lại câu hỏi ban đầu của bạn:Có, mệnh đề WHERE được kiểm tra bởi câu lệnh UPDATE, trong mọi trường hợp. Tuy nhiên, bạn phải cẩn thận với bất kỳ LỰA CHỌN nào bạn có thể đang thực hiện, để tránh xác định sai kết quả của CẬP NHẬT đó.

(Có một tùy chọn khác là thay đổi mức cô lập giao dịch. Tuy nhiên, tôi không thực sự có kinh nghiệm về điều đó và bất kỳ gotchya nào có thể tồn tại, vì vậy tôi sẽ không đi sâu vào nó.)



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Thiết lập aspnetcore với cơ sở dữ liệu MySQL trong docker

  2. Tại sao INNER JOIN không bằng (! =) Bị treo mãi mãi

  3. Chọn mục nhập theo ngày -> =NOW (), MySQL

  4. Cách lấy năm và tháng từ ngày trong MySQL

  5. được lưu trữ trong trường mysql nhưng không có ngắt dòng khi echo