Rất tiếc, giải pháp được chấp nhận là sai . Nó đúng như những gì nó nói,
Đây thực sự là ( gần như chắc chắn; xem bên dưới) phải làm gì. Nhưng sau đó nó gợi ý,
... và 1398 không kết nối với khóa. Làm thế nào điều đó có thể? 1398 là kết nối chờ đợi cho khóa. Điều này có nghĩa là nó chưa có khóa, và do đó, giết nó không có tác dụng gì. Quá trình giữ khóa sẽ vẫn giữ khóa và tiếp theo chủ đề đang cố gắng làm điều gì đó sẽ cũng dừng lại và nhập "Đang chờ khóa siêu dữ liệu" theo thứ tự.
Bạn không có gì đảm bảo rằng các quy trình "đang chờ khóa siêu dữ liệu" (WFML) cũng sẽ không chặn, nhưng bạn có thể chắc chắn rằng chỉ giết các quy trình WFML sẽ không đạt được chính xác không có gì .
Nguyên nhân thực sự là do một quy trình khác đang giữ khóa và quan trọng hơn, SHOW FULL PROCESSLIST
sẽ không cho bạn biết trực tiếp đó là cái gì .
Nó SẼ cho bạn biết nếu quá trình này đang đang thực hiện cái gì đó, có. Thường thì nó hoạt động. Ở đây, quá trình giữ khóa không thực hiện không có gì và ẩn giữa các chuỗi khác cũng không làm gì cả.
Trong trường hợp này, thủ phạm gần như chắc chắn quy trình 1396 , bắt đầu trước quá trình 1398 và hiện đang ở chế độ Sleep
và đã kéo dài 46 giây. Kể từ năm 1396 rõ ràng đã làm tất cả những gì nó cần làm (được chứng minh bằng thực tế rằng nó hiện đang ngủ và đã làm như vậy trong 46 giây, theo như MySQL có liên quan ), không có luồng nào chuyển sang chế độ ngủ trước đó có thể bị khóa (hoặc 1396 cũng sẽ bị dừng).
QUAN TRỌNG :nếu bạn đã kết nối với MySQL với tư cách là một người dùng hạn chế, hãy SHOW FULL PROCESSLIST
sẽ không hiển thị tất cả các quá trình. Vì vậy, khóa có thể bị giữ bởi một quy trình mà bạn không thấy.
SHOW PROCESSLIST
tốt hơn
SELECT ID, TIME, USER, HOST, DB, COMMAND, STATE, INFO
FROM INFORMATION_SCHEMA.PROCESSLIST WHERE DB IS NOT NULL
AND (`INFO` NOT LIKE '%INFORMATION_SCHEMA%' OR INFO IS NULL)
ORDER BY `DB`, `TIME` DESC
Ở trên có thể được điều chỉnh để chỉ hiển thị các quy trình ở trạng thái SLEEP và dù sao thì nó cũng sẽ sắp xếp chúng theo thời gian giảm dần, vì vậy sẽ dễ dàng tìm thấy quy trình bị treo hơn (nó thường là Sleep
'ing một ngay trước những cái "đang chờ khóa siêu dữ liệu").
Điều quan trọng
Để lại mọi quy trình "đang chờ khóa siêu dữ liệu" một mình .
Giải pháp nhanh và bẩn, không thực sự được khuyến khích nhưng nhanh chóng
Giết tất cả các quy trình ở trạng thái "Ngủ", trên cùng một cơ sở dữ liệu, cũ hơn cũ nhất luồng ở trạng thái "chờ khóa siêu dữ liệu". Đây là thông tin Arnaud Amaury lẽ ra đã làm:
- cho mỗi cơ sở dữ liệu có ít nhất một chuỗi trong WaitForMetadataLock:
- kết nối cũ nhất trong WFML trên DB đó hóa ra là Z giây cũ
- Tất cả các chuỗi "Ngủ" trên DB đó và cũ hơn Z đều phải chạy. Bắt đầu với những cái mới nhất, đề phòng.
- Nếu một kết nối cũ hơn và không hoạt động trên DB đó, thì có thể đó là kết nối đang giữ khóa, nhưng nó đang làm gì đó . Tất nhiên, bạn có thể giết nó, nhưng đặc biệt nếu nó là CẬP NHẬT / CHÈN / XÓA, bạn làm như vậy sẽ gặp nguy hiểm cho riêng mình.
Chín mươi chín lần trong số một trăm, chủ đề bị giết là trẻ nhất trong số những người ở trạng thái Ngủ lớn hơn hơn cái cũ hơn đang chờ khóa siêu dữ liệu:
TIME STATUS
319 Sleep
205 Sleep
19 Sleep <--- one of these two "19"
19 Sleep <--- and probably this one(*)
15 Waiting for metadata lock <--- oldest WFML
15 Waiting for metadata lock
14 Waiting for metadata lock
(*) thứ tự TIME thực sự có mili giây, hoặc lâu hơn tôi được biết, nó không hiển thị chúng. Vì vậy, trong khi cả hai quy trình đều có giá trị Thời gian là 19, giá trị thấp nhất phải nhỏ hơn.
Sửa lỗi tập trung hơn
Chạy SHOW ENGINE INNODB STATUS
và xem phần "GIAO DỊCH". Bạn sẽ tìm thấy, trong số những người khác, một cái gì đó giống như
TRANSACTION 1701, ACTIVE 58 sec;2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1
MySQL thread id 1396, OS thread handle 0x7fd06d675700, query id 1138 hostname 1.2.3.4 whatever;
Bây giờ bạn kiểm tra với SHOW FULL PROCESSLIST
id luồng 1396 đang làm gì với giao dịch # 1701 của nó. Rất có thể nó đang ở trạng thái "Ngủ". Vì vậy:một giao dịch đang hoạt động (# 1701) có khóa đang hoạt động, nó thậm chí đã thực hiện một số thay đổi vì nó có mục nhập nhật ký hoàn tác ... nhưng hiện đang không hoạt động. Đó và không có gì khác là chủ đề bạn cần giết. Mất những thay đổi đó.
Hãy nhớ rằng không làm gì trong MySQL không có nghĩa là không làm gì nói chung. Nếu bạn nhận được một số bản ghi từ MySQL và tạo CSV để tải lên FTP, thì trong quá trình tải lên FTP, kết nối MySQL không hoạt động.
Trên thực tế, nếu quy trình sử dụng MySQL và máy chủ MySQL trên cùng một máy, máy đó chạy Linux và bạn có đặc quyền root, thì có một cách để tìm ra quy trình có kết nối yêu cầu khóa. Điều này lần lượt cho phép xác định (từ việc sử dụng CPU hoặc tệ nhất là strace -ff -p pid
) liệu quá trình đó có thực sự không làm điều gì đó hay không, để giúp quyết định xem có an toàn để giết không.
Tại sao điều này xảy ra?
Tôi thấy điều này xảy ra với các ứng dụng web sử dụng kết nối MySQL "liên tục" hoặc "tổng hợp", hiện nay thường tiết kiệm rất ít thời gian:phiên bản webapp đã chấm dứt, nhưng kết nối thì không , vì vậy khóa của nó vẫn còn tồn tại ... và đang chặn những người khác.
Một cách thú vị khác mà tôi nhận thấy, trong các giả thuyết ở trên, là chạy truy vấn trả về một số hàng, và chỉ truy xuất một số hàng trong số đó . Nếu truy vấn không được đặt thành "tự động làm sạch" (tuy nhiên DBA bên dưới thực hiện điều đó), nó sẽ giữ kết nối mở và ngăn không cho toàn bộ khóa trên bảng đi qua. Tôi đã để điều này xảy ra với tôi trong một đoạn mã xác minh xem một hàng có tồn tại hay không bằng cách chọn hàng đó và xác minh xem hàng đó có lỗi (không tồn tại) hay không (nó phải tồn tại), nhưng mà không thực sự truy xuất hàng .
Hỏi DB
Một cách khác để tìm ra thủ phạm nếu bạn có MySQL gần đây, nhưng không quá gần đây vì tính năng này sẽ không được dùng nữa , là (bạn cần lại đặc quyền trên giản đồ thông tin)
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS
WHERE LOCK_TRX_ID IN
(SELECT BLOCKING_TRX_ID FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS);
Giải pháp thực tế, đòi hỏi thời gian và công việc
Sự cố thường do kiến trúc này gây ra:
Khi ứng dụng web chết hoặc phiên bản chuỗi nhẹ của ứng dụng web chết, vùng chứa / nhóm kết nối có thể không . Và nó là thùng chứa giữ cho kết nối luôn mở, vì vậy rõ ràng là kết nối không đóng. Khá dễ đoán, MySQL không coi hoạt động đã hoàn tất .
Nếu ứng dụng web không tự dọn dẹp (không có ROLLBACK
hoặc COMMIT
cho một giao dịch, không có UNLOCK TABLES
, v.v.), thì bất cứ điều gì mà ứng dụng web bắt đầu làm đều vẫn còn tồn tại và có thể vẫn đang chặn những người khác.
Sau đó có hai giải pháp. Điều tệ hơn là giảm thời gian chờ không hoạt động
. Nhưng hãy đoán xem điều gì sẽ xảy ra nếu bạn đợi quá lâu giữa hai truy vấn (chính xác là:"Máy chủ MySQL đã biến mất"). Sau đó, bạn có thể sử dụng mysql_ping
nếu có (sắp không được dùng nữa. Có các giải pháp thay thế
cho PDO. Hoặc bạn có thể kiểm tra điều đó và mở lại kết nối nếu nó xảy ra (đây là cách Python). Vì vậy - với một khoản phí hiệu suất nhỏ - điều đó hoàn toàn khả thi.
Giải pháp tốt hơn, thông minh hơn ít dễ thực hiện hơn. Cố gắng làm sạch tập lệnh, đảm bảo truy xuất tất cả các hàng hoặc giải phóng tất cả tài nguyên truy vấn, bắt tất cả các ngoại lệ và xử lý chúng đúng cách hoặc nếu có thể bỏ qua hoàn toàn các kết nối liên tục . Hãy để mỗi phiên bản tạo kết nối riêng hoặc sử dụng người điều khiển hồ bơi
(trong PHP PDO, sử dụng PDO::ATTR_PERSISTENT
được đặt rõ ràng thành false
). Ngoài ra (ví dụ:trong PHP), bạn có thể yêu cầu trình xử lý hủy và xử lý ngoại lệ buộc làm sạch kết nối bằng cách cam kết hoặc khôi phục các giao dịch và phát hành mở khóa bảng rõ ràng.
Tôi không biết cách truy vấn các tài nguyên tập kết quả hiện có để giải phóng chúng; cách duy nhất sẽ là tiết kiệm các tài nguyên đó trong một mảng riêng tư.