Đặt giới hạn thời gian cho giao dịch trong MySQL / InnoDB


11

Điều này xuất phát từ câu hỏi liên quan này , nơi tôi muốn biết làm thế nào để buộc hai giao dịch xảy ra tuần tự trong một trường hợp tầm thường (trong đó cả hai chỉ hoạt động trên một hàng duy nhất). Tôi nhận được câu trả lời sử dụng SELECT ... FOR UPDATEnhư là dòng đầu tiên của cả hai giao dịch, nhưng điều này dẫn đến một vấn đề: Nếu giao dịch đầu tiên không bao giờ được cam kết hoặc quay trở lại, thì giao dịch thứ hai sẽ bị chặn vô thời hạn. Các innodb_lock_wait_timeoutbiến đặt số giây sau đó khách hàng cố gắng để thực hiện giao dịch thứ hai sẽ được thông báo "Xin lỗi, hãy thử lại" ... nhưng như xa như tôi có thể nói, họ sẽ phải cố gắng một lần nữa cho đến khi máy chủ khởi động lại tiếp theo. Vì thế:

  1. Chắc chắn phải có một cách để buộc một ROLLBACKnếu một giao dịch được thực hiện mãi mãi? Tôi phải sử dụng một daemon để giết các giao dịch như vậy, và nếu vậy, một daemon như vậy sẽ trông như thế nào?
  2. Nếu một kết nối bị giết bởi wait_timeouthoặc interactive_timeoutgiữa giao dịch, giao dịch có bị khôi phục không? Có cách nào để kiểm tra điều này từ bàn điều khiển không?

Làm rõ : innodb_lock_wait_timeoutđặt số giây mà giao dịch sẽ đợi khóa được phát hành trước khi từ bỏ; những gì tôi muốn là một cách buộc một khóa được phát hành.

Cập nhật 1 : Đây là một ví dụ đơn giản cho thấy tại sao innodb_lock_wait_timeoutkhông đủ để đảm bảo rằng giao dịch thứ hai không bị chặn bởi giao dịch thứ nhất:

START TRANSACTION;
SELECT SLEEP(55);
COMMIT;

Với cài đặt mặc định là innodb_lock_wait_timeout = 50, giao dịch này hoàn thành không có lỗi sau 55 giây. Và nếu bạn thêm một dòng UPDATEtrước SLEEPdòng, sau đó bắt đầu một giao dịch thứ hai từ một khách hàng khác cố gắng thực SELECT ... FOR UPDATEhiện cùng một hàng, đó là giao dịch thứ hai hết thời gian, không phải là giao dịch ngủ quên.

Những gì tôi đang tìm kiếm là một cách để chấm dứt tình trạng uể oải của giao dịch này.

Cập nhật 2 : Đáp lại những lo ngại của hobodave về mức độ thực tế của ví dụ trên, đây là một kịch bản thay thế: Một DBA kết nối với một máy chủ trực tiếp và chạy

START TRANSACTION
SELECT ... FOR UPDATE

trong đó dòng thứ hai khóa một hàng mà ứng dụng thường ghi vào. Sau đó, DBA bị gián đoạn và bỏ đi, quên kết thúc giao dịch. Ứng dụng sẽ dừng lại cho đến khi hàng được mở khóa. Tôi muốn giảm thiểu thời gian ứng dụng bị kẹt do lỗi này.


Bạn vừa cập nhật câu hỏi của bạn để hoàn toàn xung đột với câu hỏi ban đầu của bạn. Bạn nêu in đậm "Nếu giao dịch đầu tiên là không bao giờ cam kết hoặc cuộn lại, sau đó giao dịch thứ hai sẽ bị chặn vô thời hạn." Bạn từ chối điều này với bản cập nhật mới nhất của bạn: "đó là giao dịch thứ hai hết thời gian, không phải là giao dịch ngủ quên". -- nghiêm túc?!
hobodave

Tôi cho rằng phrasing của tôi có thể đã rõ ràng hơn. Bởi "bị chặn vô thời hạn", tôi có nghĩa là giao dịch thứ hai sẽ hết thời gian với thông báo lỗi có nội dung "thử khởi động lại giao dịch"; nhưng làm như vậy sẽ không có kết quả, vì nó sẽ hết thời gian một lần nữa. Vì vậy, giao dịch thứ hai bị chặn, nó sẽ không bao giờ hoàn thành, vì nó sẽ hết thời gian.
Trevor Burnham

@Trevor: Phrasing của bạn đã hoàn toàn rõ ràng. Bạn chỉ đơn giản là cập nhật câu hỏi của bạn để hỏi một điều hoàn toàn khác, đó là hình thức cực kỳ xấu.
hobodave

Câu hỏi luôn luôn giống nhau: Vấn đề là giao dịch thứ hai bị chặn vô thời hạn khi giao dịch đầu tiên không bao giờ được cam kết hoặc quay trở lại. Tôi muốn thực hiện một ROLLBACKgiao dịch đầu tiên nếu mất hơn nvài giây để hoàn thành. Có cách nào để làm như vậy?
Trevor Burnham

1
@TrevorBurnham Tôi cũng tự hỏi tại sao MYSQLkhông có cấu hình để ngăn chặn tình huống này. Bởi vì nó không được chấp nhận khi treo máy chủ do khách hàng thiếu trách nhiệm. Tôi không thấy khó khăn gì để hiểu câu hỏi của bạn, nó cũng rất phù hợp.
Dinoop paloli

Câu trả lời:



3

Vì câu hỏi của bạn được hỏi ở đây trên ServerFault, thật hợp lý khi cho rằng bạn đang tìm kiếm giải pháp MySQL cho vấn đề MySQL, đặc biệt là trong lĩnh vực kiến ​​thức mà quản trị viên hệ thống và / hoặc DBA sẽ có chuyên môn. phần giải quyết các câu hỏi của bạn:

Nếu giao dịch đầu tiên không bao giờ được cam kết hoặc quay trở lại, thì giao dịch thứ hai sẽ bị chặn vô thời hạn

Không, nó sẽ không. Tôi nghĩ bạn không hiểu innodb_lock_wait_timeout. Nó làm chính xác những gì bạn cần.

Nó sẽ trở lại với một lỗi như được nêu trong hướng dẫn:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • Theo định nghĩa, điều này không phải là vô thời hạn . Nếu ứng dụng của bạn được kết nối lại và chặn liên tục thì ứng dụng của bạn sẽ "chặn vô thời hạn", không phải là giao dịch. Các giao dịch thứ hai chặn rất chắc chắn trong innodb_lock_wait_timeoutvài giây.

Theo mặc định, giao dịch sẽ không được khôi phục. Mã ứng dụng của bạn có trách nhiệm quyết định cách xử lý lỗi này, cho dù đó là thử lại hay quay lại.

Nếu bạn muốn tự động quay lại, điều đó cũng được giải thích trong hướng dẫn:

Giao dịch hiện tại không được khôi phục. (Để quay lại toàn bộ giao dịch, hãy khởi động máy chủ với --innodb_rollback_on_timeouttùy chọn.


RE: Nhiều cập nhật và bình luận của bạn

Đầu tiên, bạn đã tuyên bố trong các bình luận của mình rằng bạn "có nghĩa" nói rằng bạn muốn có một cách để hết thời gian giao dịch đầu tiên bị chặn vô thời hạn. Điều này không rõ ràng từ câu hỏi ban đầu của bạn và mâu thuẫn với "Nếu giao dịch đầu tiên không bao giờ được cam kết hoặc quay trở lại, thì giao dịch thứ hai sẽ bị chặn vô thời hạn".

Tuy nhiên, tôi cũng có thể trả lời câu hỏi đó. Giao thức MySQL không có "thời gian chờ truy vấn". Điều này có nghĩa là bạn không thể hết thời gian giao dịch bị chặn đầu tiên. Bạn phải đợi cho đến khi nó kết thúc, hoặc giết phiên. Khi phiên bị hủy, máy chủ sẽ tự động khôi phục giao dịch.

Cách khác chỉ là sử dụng hoặc viết thư viện mysql sử dụng I / O không chặn, cho phép ứng dụng của bạn tiêu diệt luồng / ngã ba thực hiện truy vấn sau N giây. Việc triển khai và sử dụng một thư viện như vậy nằm ngoài phạm vi của ServerFault. Đây là một câu hỏi thích hợp cho StackOverflow .

Thứ hai, bạn đã nêu những điều sau trong ý kiến ​​của bạn:

Tôi thực sự quan tâm nhiều hơn đến câu hỏi của mình với một kịch bản trong đó ứng dụng khách bị treo (giả sử, bị kẹt trong một vòng lặp vô hạn) trong quá trình giao dịch so với một giao dịch mất nhiều thời gian khi kết thúc MySQL.

Điều này hoàn toàn không rõ ràng trong câu hỏi ban đầu của bạn và vẫn không có. Điều này chỉ có thể được nhận ra sau khi bạn chia sẻ miếng ngon khá quan trọng này trong bình luận.

Nếu đây thực sự là vấn đề bạn đang cố gắng giải quyết, thì tôi sợ bạn đã hỏi nó trên diễn đàn sai. Bạn đã mô tả một vấn đề lập trình cấp ứng dụng đòi hỏi một giải pháp lập trình, một vấn đề mà MySQL không thể cung cấp và nằm ngoài phạm vi của cộng đồng này. Câu trả lời mới nhất của bạn trả lời câu hỏi "Làm cách nào để ngăn chương trình Ruby lặp lại vô hạn?". Câu hỏi đó không có chủ đề cho cộng đồng này và nên được hỏi trên StackOverflow .


Xin lỗi, nhưng trong khi đó là sự thật khi giao dịch đang chờ khóa (như tên và tài liệu innodb_lock_wait_timeoutđề xuất), thì không phải như vậy trong tình huống tôi đặt ra: nơi khách hàng bị treo hoặc gặp sự cố trước khi thay đổi để thực hiện COMMIThoặc ROLLBACK.
Trevor Burnham

@Trevor: Bạn đơn giản là sai. Điều này là tầm thường để kiểm tra kết thúc của bạn. Tôi chỉ thử nó vào cuối của tôi. Vui lòng lấy lại downvote của bạn.
hobodave

@hobo, chỉ vì quyền của bạn không có nghĩa là anh ấy phải thích nó.
Chris S

Chris, bạn có thể giải thích anh ấy đúng không? Giao dịch thứ hai sẽ xảy ra như thế nào nếu không có COMMIThoặc ROLLBACKtin nhắn được gửi để giải quyết giao dịch đầu tiên? Hobodave, có lẽ sẽ hữu ích nếu bạn trình bày mã kiểm tra của mình.
Trevor Burnham

2
@Trevor: Bạn không có ý nghĩa gì cả. Bạn muốn tuần tự hóa các giao dịch, phải không? Vâng, bạn đã nói như vậy trong câu hỏi khác của bạn và lặp lại rằng ở đây trong câu hỏi này. Nếu giao dịch đầu tiên chưa hoàn thành, làm thế nào bạn có thể mong đợi giao dịch thứ hai hoàn thành? Bạn đã nói rõ ràng rằng hãy đợi khóa được phát hành trên hàng đó. Do đó, nó phải chờ. Bạn không hiểu gì về điều này?
hobodave

1

Trong một tình huống tương tự, nhóm của tôi đã quyết định sử dụng pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Nó có thể báo cáo hoặc giết các truy vấn mà (trong số những thứ khác) đang chạy quá lâu. Sau đó, có thể chạy nó trong một cron mỗi X phút.

Vấn đề là một truy vấn có thời gian thực hiện dài bất ngờ (ví dụ không xác định) đã khóa một thủ tục sao lưu đã cố gắng xóa các bảng bằng khóa đọc, sau đó khóa tất cả các truy vấn khác trên "chờ bảng để xóa" mà sau đó không bao giờ hết thời gian bởi vì họ không chờ khóa, dẫn đến không có lỗi trong ứng dụng, điều này cuối cùng dẫn đến không có thông báo nào cho quản trị viên và ứng dụng tạm dừng.

Khắc phục rõ ràng là để sửa truy vấn ban đầu, nhưng để tránh tình huống vô tình xảy ra trong tương lai, sẽ rất tuyệt nếu có thể tự động hủy (hoặc báo cáo) các giao dịch / truy vấn kéo dài hơn thời gian cụ thể, mà tác giả của câu hỏi dường như đang hỏi. Điều này có thể thực hiện được với pt-kill.


0

Một giải pháp đơn giản sẽ là có một thủ tục được lưu trữ để tiêu diệt các truy vấn mất nhiều thời gian hơn timeoutthời gian cần thiết và innodb_rollback_on_timeouttùy chọn sử dụng cùng với nó.


-2

Sau khi Googling đi loanh quanh một lúc, có vẻ như không có cách nào trực tiếp để đặt giới hạn thời gian cho mỗi giao dịch. Đây là giải pháp tốt nhất tôi có thể tìm thấy:

Lệnh

SHOW PROCESSLIST;

cung cấp cho bạn một bảng với tất cả các lệnh hiện đang được chạy và thời gian (tính bằng giây) kể từ khi chúng bắt đầu. Khi một khách hàng đã ngừng đưa ra các lệnh, thì họ được mô tả là đưa ra một Sleeplệnh, với Timecột là số giây kể từ khi lệnh cuối cùng của họ hoàn thành. Vì vậy, bạn có thể truy cập thủ công và KILLmọi thứ có Timegiá trị hơn, giả sử, 5 giây nếu bạn tự tin rằng không có truy vấn nào sẽ mất hơn 5 giây trên cơ sở dữ liệu của bạn. Chẳng hạn, nếu tiến trình với id 3 có giá trị 12 trong Timecột của nó , bạn có thể làm

KILL 3;

Tài liệu về cú pháp KILL cho thấy rằng một giao dịch được thực hiện bởi luồng với id đó sẽ được khôi phục (mặc dù tôi chưa kiểm tra điều này, vì vậy hãy thận trọng).

Nhưng làm thế nào để tự động hóa điều này và giết tất cả các tập lệnh ngoài giờ cứ sau 5 giây? Nhận xét đầu tiên trên cùng trang đó cho chúng ta một gợi ý, nhưng mã là trong PHP; có vẻ như chúng ta không thể làm một SELECTtrên SHOW PROCESSLISTtừ bên trong client MySQL. Tuy nhiên, giả sử chúng ta có một trình nền PHP mà chúng ta chạy mỗi $MAX_TIMEgiây, nó có thể trông giống như thế này:

$result = mysql_query("SHOW FULL PROCESSLIST");
while ($row=mysql_fetch_array($result)) {
  $process_id=$row["Id"];
    if ($row["Time"] > $MAX_TIME) {
      $sql="KILL $process_id";
      mysql_query($sql);
  }
}

Lưu ý rằng điều này sẽ có tác dụng phụ là buộc tất cả các kết nối máy khách nhàn rỗi phải kết nối lại, không chỉ các kết nối hoạt động sai.

Nếu bất cứ ai có câu trả lời tốt hơn, tôi rất muốn nghe nó.

Chỉnh sửa: Có ít nhất một rủi ro nghiêm trọng khi sử dụng KILLtheo cách này. Giả sử rằng bạn sử dụng tập lệnh ở trên cho KILLmọi kết nối không hoạt động trong 10 giây. Sau khi kết nối không hoạt động trong 10 giây, nhưng trước khi KILLkết nối được cấp, người dùng có kết nối bị giết sẽ ra START TRANSACTIONlệnh. Sau đó họ KILLed. Họ gửi một UPDATEvà sau đó, suy nghĩ hai lần, vấn đề a ROLLBACK. Tuy nhiên, vì KILLđã khôi phục giao dịch ban đầu của họ, bản cập nhật đã được xử lý ngay lập tức và không thể quay lại! Xem bài đăng này cho một trường hợp thử nghiệm.


2
Nhận xét này là dành cho độc giả tương lai của câu trả lời này. Xin đừng làm điều này, ngay cả khi đó là câu trả lời được chấp nhận.
hobodave

OK, thay vì hạ thấp và để lại một bình luận snide, làm thế nào để giải thích tại sao điều này sẽ là một ý tưởng tồi? Người đã đăng đoạn mã PHP gốc nói rằng nó giữ cho máy chủ của họ không bị treo; nếu có cách tốt hơn để đạt được cùng một mục tiêu, hãy chỉ cho tôi.
Trevor Burnham

6
Các bình luận không phải là ngáy. Đó là một cảnh báo cho tất cả các độc giả trong tương lai không cố gắng sử dụng "giải pháp" của bạn. Tự động giết chết các giao dịch chạy dài là một ý tưởng khủng khiếp đơn phương. Nó không bao giờ nên được thực hiện . Đó là lời giải thích. Giết chết phiên nên là một ngoại lệ, không phải là một tiêu chuẩn. Nguyên nhân sâu xa của vấn đề giả định của bạn là lỗi người dùng. Bạn không tạo các giải pháp kỹ thuật cho các DBA khóa hàng và sau đó "bỏ đi". Bạn bắn họ.
hobodave

-2

Như hobodave đã đề xuất trong một nhận xét, có thể ở một số khách hàng (mặc dù không rõ ràng là mysqltiện ích dòng lệnh) để đặt giới hạn thời gian cho giao dịch. Đây là một bản trình diễn sử dụng ActiveRecord trong Ruby:

require 'rubygems'
require 'timeout'
require 'active_record'

Timeout::timeout(5) {
  Foo.transaction do
    Foo.create(:name => 'Bar')
    sleep 10
  end
}

Trong ví dụ này, giao dịch hết thời gian sau 5 giây và được tự động khôi phục . ( Cập nhật để đáp ứng với bình luận hobodave của: . Nếu cơ sở dữ liệu phải mất hơn 5 giây để phản ứng, giao dịch sẽ được cuộn lại càng sớm càng nó-không sớm hơn) Nếu bạn muốn đảm bảo rằng tất cả các thời gian giao dịch của bạn ra sau ngiây, bạn có thể xây dựng một trình bao bọc xung quanh ActiveRecord. Tôi đoán rằng điều này cũng áp dụng cho các thư viện phổ biến nhất trong Java, .NET, Python, v.v., nhưng tôi chưa thử nghiệm chúng. (Nếu bạn có, xin vui lòng gửi bình luận về câu trả lời này.)

Giao dịch trong ActiveRecord cũng có lợi thế là an toàn nếu KILLđược phát hành, không giống như các giao dịch được thực hiện từ dòng lệnh. Xem /dba/1561/mysql-client- Belief-theyre-in-a-transaction-gets-kiled-wreaks-havoc .

Dường như không thể thực thi thời gian giao dịch tối đa ở phía máy chủ, ngoại trừ thông qua một tập lệnh như đoạn tôi đã đăng trong câu trả lời khác của mình.


Bạn đã bỏ qua rằng ActiveRecord sử dụng I / O đồng bộ (chặn). Tất cả các kịch bản đang làm là làm gián đoạn lệnh ngủ Ruby. Nếu Foo.createlệnh của bạn bị chặn trong 5 phút, Timeout sẽ không giết nó. Điều này không tương thích với ví dụ được cung cấp trong câu hỏi của bạn. Một SELECT ... FOR UPDATEcó thể dễ dàng chặn trong thời gian dài, như bất kỳ truy vấn nào khác.
hobodave

Cần lưu ý rằng gần như tất cả các thư viện máy khách mysql đều sử dụng chặn I / O, do đó gợi ý trong câu trả lời của tôi là bạn sẽ cần sử dụng hoặc viết riêng I / O không sử dụng. Dưới đây là một minh chứng đơn giản về việc Hết giờ là vô dụng: gist.github.com/856100
hobodave

Tôi thực sự quan tâm nhiều hơn đến câu hỏi của mình với một kịch bản trong đó ứng dụng khách bị treo (giả sử, bị kẹt trong một vòng lặp vô hạn) trong quá trình giao dịch so với một giao dịch mất nhiều thời gian khi kết thúc MySQL. Nhưng cảm ơn, đó là một điểm tốt. Tôi đã cập nhật câu hỏi cần lưu ý.)
Trevor Burnham

Tôi không thể sao chép kết quả yêu cầu của bạn. Tôi đã cập nhật ý chính để sử dụng ActiveRecord::Base#find_by_sql: gist.github.com/856100 Một lần nữa, điều này là do AR sử dụng chặn I / O.
hobodave

@hobodave Có, chỉnh sửa đó là sai và đã được sửa. Để rõ ràng: timeoutPhương thức sẽ khôi phục giao dịch, nhưng cho đến khi cơ sở dữ liệu MySQL trả lời một truy vấn. Vì vậy, timeoutphương pháp này phù hợp để ngăn chặn các giao dịch dài ở phía khách hàng hoặc các giao dịch dài bao gồm một số truy vấn tương đối ngắn, nhưng không phải cho các giao dịch dài trong đó một truy vấn duy nhất là thủ phạm.
Trevor Burnham
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.