Khóa hàng InnoDB - cách thực hiện


13

Bây giờ tôi đã tìm kiếm xung quanh, đọc trang web mysql và tôi vẫn không thể thấy chính xác nó hoạt động như thế nào.

Tôi muốn chọn và hàng khóa kết quả để viết, viết thay đổi và phát hành khóa. audocommit là trên.

kế hoạch

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime) 

Chọn một mục có trạng thái chờ xử lý và cập nhật nó để hoạt động. Sử dụng một ghi độc quyền để đảm bảo rằng cùng một mục không được chọn hai lần.

vì thế;

"SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR WRITE"

lấy id từ kết quả

"UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>

Tôi có cần phải làm gì để giải phóng khóa không và nó có hoạt động như tôi đã làm ở trên không?

Câu trả lời:


26

Những gì bạn muốn là CHỌN ... ĐỂ CẬP NHẬT từ trong bối cảnh giao dịch. CHỌN CẬP NHẬT đặt một khóa độc quyền trên các hàng đã chọn, giống như khi bạn đang thực hiện CẬP NHẬT. Nó cũng ngầm chạy ở mức cô lập READ CAMITED bất kể mức cô lập được đặt rõ ràng là gì. Chỉ cần lưu ý rằng CHỌN ... ĐỂ CẬP NHẬT rất tệ cho đồng thời và chỉ nên được sử dụng khi thực sự cần thiết. Nó cũng có xu hướng nhân lên trong một cơ sở mã khi mọi người cắt và dán.

Đây là một phiên ví dụ từ cơ sở dữ liệu Sakila thể hiện một số hành vi của các truy vấn FOR UPDATE.

Đầu tiên, để chúng ta rõ ràng, hãy đặt mức cô lập giao dịch thành ĐỌC LẠI. Điều này thường không cần thiết, vì nó là mức cô lập mặc định cho 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)    

Trong phiên khác, cập nhật hàng này. Linda kết hôn và đổi tên:

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

Quay trở lại với phiên 1, vì chúng tôi đã ở chế độ ĐỌC LẠI, Linda vẫn là LINDA WILLIAM:

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)

Nhưng bây giờ, chúng tôi muốn độc quyền truy cập vào hàng này, vì vậy chúng tôi gọi CHO CẬP NHẬT trên hàng. Lưu ý rằng bây giờ chúng tôi nhận được phiên bản mới nhất của hàng trở lại, đã được cập nhật trong phiên 2 bên ngoài giao dịch này. Đó không phải là ĐỌC LẠI, đó là ĐỌC CAM KẾT

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)

Hãy kiểm tra khóa được đặt trong phiên1. Lưu ý rằng session2 không thể cập nhật hàng.

session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

Nhưng chúng ta vẫn có thể chọn từ nó

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)

Và chúng ta vẫn có thể cập nhật một bảng con có mối quan hệ khóa ngoại

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;

Một tác dụng phụ khác là bạn tăng đáng kể xác suất gây ra bế tắc.

Trong trường hợp cụ thể của bạn, bạn có thể muốn:

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;

Nếu phần "làm một số thứ khác" là không cần thiết và bạn thực sự không cần phải giữ thông tin về hàng đó, thì CHỌN CẬP NHẬT là không cần thiết và lãng phí và thay vào đó bạn chỉ có thể chạy cập nhật:

UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;

Hy vọng điều này làm cho một số ý nghĩa.


3
Cảm ơn. Dường như không giải quyết được vấn đề của tôi, khi hai luồng đang đến với "CHỌN id TỪ itemsWHERE status= 'đang chờ xử lý' GIỚI HẠN 1 ĐỂ CẬP NHẬT;" và cả hai đều nhìn thấy cùng một hàng, sau đó một cái sẽ khóa cái kia. Tôi đã hy vọng bằng cách nào đó nó sẽ có thể vượt qua hàng bị khóa và goto mục tiếp theo đang chờ xử lý ..
Wizzard

1
Bản chất của cơ sở dữ liệu là chúng trả về dữ liệu nhất quán. Nếu bạn thực hiện truy vấn đó hai lần trước khi giá trị được cập nhật, bạn sẽ nhận lại kết quả tương tự. Không có "lấy cho tôi giá trị đầu tiên phù hợp với truy vấn này, trừ khi hàng bị khóa" tiện ích mở rộng SQL mà tôi biết. Điều này nghe có vẻ đáng ngờ giống như bạn đang thực hiện một hàng đợi trên cơ sở dữ liệu quan hệ. Có phải vậy không?
Aaron Brown

Aaron; vâng đó là những gì tôi đang cố gắng làm. Tôi đã xem xét việc sử dụng một cái gì đó như gearman - nhưng đó là một bức tượng bán thân. Bạn có điều gì khác trong tâm trí?
Wizzard

Tôi nghĩ bạn nên đọc cái này: engineyard.com/blog/2011/ trên - đối với hàng đợi tin nhắn, có rất nhiều trong số chúng tùy thuộc vào ngôn ngữ khách hàng của bạn. ActiveMQ, Resque (Ruby + Redis), ZeroMQ, RabbitMQ, v.v.
Aaron Brown

Làm cách nào để tôi thực hiện sao cho phiên 2 chặn đọc cho đến khi cập nhật trong phiên 1?
CMCDragonkai

2

Nếu bạn đang sử dụng công cụ lưu trữ InnoDB, nó sử dụng khóa cấp hàng. Kết hợp với đa phiên bản, điều này dẫn đến sự tương tranh truy vấn tốt vì một bảng nhất định có thể được đọc và sửa đổi bởi các khách hàng khác nhau cùng một lúc. Các thuộc tính đồng thời mức hàng như sau:

Các khách hàng khác nhau có thể đọc cùng một hàng cùng một lúc.

Các khách hàng khác nhau có thể sửa đổi các hàng khác nhau cùng một lúc.

Các khách hàng khác nhau không thể sửa đổi cùng một hàng cùng một lúc. Nếu một giao dịch sửa đổi một hàng, các giao dịch khác không thể sửa đổi cùng một hàng cho đến khi giao dịch đầu tiên hoàn thành. Các giao dịch khác cũng không thể đọc hàng được sửa đổi, trừ khi chúng đang sử dụng mức cách ly READ UNCOMMITTED. Đó là, họ sẽ thấy hàng không thay đổi ban đầu.

Về cơ bản, bạn không phải chỉ định khóa rõ ràng InnoDB xử lý nó mặc dù trong một số trường hợp, bạn có thể phải cung cấp chi tiết khóa rõ ràng về khóa rõ ràng được đưa ra dưới đây:

Danh sách sau đây mô tả các loại khóa có sẵn và tác dụng của chúng:

ĐỌC

Khóa một cái bàn để đọc. Khóa READ khóa một bảng để đọc các truy vấn, chẳng hạn như CHỌN lấy dữ liệu từ bảng. Nó không cho phép các hoạt động ghi như CHERTN, XÓA hoặc CẬP NHẬT sửa đổi bảng, ngay cả bởi máy khách giữ khóa. Khi một bảng bị khóa để đọc, các máy khách khác có thể đọc từ bảng cùng một lúc, nhưng không khách hàng nào có thể ghi vào bảng. Một khách hàng muốn ghi vào một bảng đã bị khóa phải đợi cho đến khi tất cả các khách hàng đang đọc từ đó đã hoàn thành và giải phóng các khóa của họ.

VIẾT

Khóa một bảng để viết. Một khóa VIẾT là một khóa độc quyền. Nó chỉ có thể có được khi một bảng không được sử dụng. Sau khi có được, chỉ có khách hàng giữ khóa ghi có thể đọc hoặc ghi vào bảng. Các khách hàng khác không thể đọc và viết cho nó. Không có khách hàng nào khác có thể khóa bảng để đọc hoặc viết.

ĐỌC ĐỊA PHƯƠNG

Khóa một bảng để đọc, nhưng cho phép chèn đồng thời. Một chèn đồng thời là một ngoại lệ đối với nguyên tắc "người đọc khối nhà văn". Nó chỉ áp dụng cho các bảng MyISAM. Nếu bảng MyISAM không có lỗ ở giữa do các bản ghi bị xóa hoặc cập nhật, việc chèn luôn diễn ra ở cuối bảng. Trong trường hợp đó, một khách hàng đang đọc từ bảng có thể khóa nó bằng khóa READ LOCAL để cho phép các khách hàng khác chèn vào bảng trong khi khách hàng giữ khóa đọc đọc từ nó. Nếu bảng MyISAM không có lỗ, bạn có thể xóa chúng bằng cách sử dụng BẢNG TỐI ƯU để chống phân mảnh bảng.


cảm ơn câu trả lời Khi tôi có bảng này và 100 khách hàng đang kiểm tra các mặt hàng đang chờ xử lý, tôi đã nhận được rất nhiều va chạm - 2-3 khách hàng nhận được cùng một hàng chờ xử lý. Khóa bảng là để chậm.
Wizzard

0

Một cách khác là thêm một cột lưu trữ thời gian của khóa thành công cuối cùng và sau đó mọi thứ khác muốn khóa hàng sẽ cần đợi cho đến khi nó bị xóa hoặc 5 phút (hoặc bất cứ điều gì) đã trôi qua.

Cái gì đó như...

Schema

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime)
lastlock (int)

Lastlock là một int vì nó lưu dấu thời gian unix vì nó dễ dàng hơn (và có thể nhanh hơn) để so sánh với.

// Xin lỗi về ngữ nghĩa, tôi chưa kiểm tra họ chạy một cách chính xác, nhưng họ sẽ đủ gần nếu họ không.

UPDATE items 
  SET lastlock = UNIX_TIMESTAMP() 
WHERE 
  lastlock = 0
  OR (UNIX_TIMESTAMP() - lastlock) > 360;

Sau đó kiểm tra xem có bao nhiêu hàng đã được cập nhật, bởi vì các hàng không thể được cập nhật bởi hai quy trình cùng một lúc, nếu bạn cập nhật hàng, bạn đã có khóa. Giả sử bạn đang sử dụng PHP, bạn sẽ sử dụng mysql_affected_rows (), nếu lợi nhuận từ đó là 1, bạn đã khóa thành công.

Sau đó, bạn có thể cập nhật khóa cuối thành 0 sau khi bạn đã hoàn thành những gì bạn cần làm hoặc lười biếng và đợi 5 phút khi lần thử khóa tiếp theo sẽ thành công.

EDIT: Bạn có thể cần một chút công việc để kiểm tra nó hoạt động như mong đợi vào khoảng thời gian mùa hè thay đổi vì đồng hồ sẽ quay trở lại một giờ, có lẽ khiến khoảng trống kiểm tra. Bạn cần đảm bảo dấu thời gian unix trong UTC - dù sao chúng cũng có thể.


-1

Ngoài ra, bạn có thể phân đoạn các trường bản ghi để cho phép viết song song và bỏ qua khóa hàng (kiểu cặp json bị phân mảnh). Vì vậy, nếu một trường của bản ghi hợp chất là số nguyên / số thực, bạn có thể có đoạn 1-8 của trường đó (8 bản ghi ghi / hàng có hiệu lực). Sau đó tổng hợp các mảnh vỡ tròn sau mỗi lần viết vào một tra cứu đọc riêng biệt. Điều này cho phép tối đa 8 người dùng đồng thời song song.

Vì bạn chỉ làm việc với mỗi phân đoạn tạo ra một phần tổng, không có xung đột và cập nhật song song thực sự (tức là bạn viết khóa từng đoạn chứ không phải toàn bộ bản ghi đọc thống nhất). Điều này chỉ hoạt động trên các lĩnh vực số rõ ràng. Một cái gì đó dựa vào sửa đổi toán học để lưu trữ một kết quả.

Do đó, nhiều đoạn ghi trên mỗi trường đọc thống nhất trên mỗi bản ghi đọc thống nhất. Những mảnh số này cũng tự cho vay ECC, mã hóa và chuyển / lưu trữ mức khối. Càng có nhiều đoạn ghi, tốc độ truy cập ghi song song / đồng thời trên dữ liệu bão hòa càng lớn.

MMORPG chịu ảnh hưởng lớn từ vấn đề này, khi một số lượng lớn người chơi bắt đầu đánh nhau với các kỹ năng Area of ​​Effect. Tất cả những người chơi đó đều cần phải viết / cập nhật mọi người chơi khác cùng một lúc, song song, tạo ra một cơn bão khóa ghi trên các bản ghi người chơi thống nhất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.