Truy tìm, gỡ lỗi và sửa lỗi Row Lock Contentions


12

Cuối cùng, tôi đã phải đối mặt với rất nhiều sự tranh chấp khóa hàng. Bảng trong tranh chấp dường như là một bảng cụ thể.

Đây thường là những gì xảy ra -

  • Nhà phát triển 1 bắt đầu giao dịch từ màn hình giao diện người dùng của Oracle Forms
  • Nhà phát triển 2 bắt đầu một giao dịch khác, từ một phiên khác sử dụng cùng một màn hình

~ 5 phút sau, mặt trước có vẻ không phản hồi. Các phiên kiểm tra cho thấy sự tranh chấp khóa hàng. "Giải pháp" mà mọi người ném xung quanh là giết phiên: /

Là một nhà phát triển cơ sở dữ liệu

  • Những gì có thể được thực hiện để loại bỏ tranh chấp khóa hàng?
  • Có thể tìm ra dòng nào của một thủ tục được lưu trữ đang gây ra sự tranh chấp khóa hàng này không
  • Điều gì sẽ là hướng dẫn chung để giảm / tránh / loại bỏ các vấn đề như vậy mà mã hóa?

Nếu câu hỏi này cảm thấy quá mở / không đủ thông tin, vui lòng chỉnh sửa / cho tôi biết - tôi sẽ cố gắng hết sức để thêm vào một số thông tin bổ sung.


Bảng được đề cập nằm trong rất nhiều phần chèn và cập nhật, tôi muốn nói rằng đây là một trong những bảng bận rộn nhất. SP khá phức tạp - để đơn giản hóa - nó lấy dữ liệu từ nhiều bảng khác nhau, đưa nó vào các bảng làm việc, rất nhiều thao tác số học xảy ra trên bảng làm việc và kết quả của bảng làm việc được chèn / cập nhật vào bảng trong câu hỏi.


Phiên bản cơ sở dữ liệu là Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit. Luồng logic được thực hiện theo cùng một thứ tự trong cả hai phiên, giao dịch không được mở quá lâu (hoặc ít nhất là tôi nghĩ vậy) và các khóa xảy ra trong quá trình thực hiện giao dịch.


Cập nhật: Số lượng hàng của bảng lớn hơn tôi dự kiến, vào khoảng 3,1 triệu hàng. Ngoài ra, sau khi theo dõi một phiên, tôi thấy rằng một vài câu lệnh cập nhật cho bảng này không sử dụng chỉ mục. Tại sao nó lại như vậy - tôi không chắc chắn. Cột được tham chiếu trong mệnh đề where được lập chỉ mục. Tôi hiện đang xây dựng lại chỉ mục.


1
@Sathya - bạn có thể xây dựng sự phức tạp của thủ tục lưu trữ không? bảng nghi ngờ đang được cập nhật nghiêm ngặt hoặc chèn?
CoderHawk

Các khóa ngoại có đóng vai trò ở đây không? (Đôi khi điều này cần một chỉ mục) Phiên bản cơ sở dữ liệu nào được đưa ra? Là luồng logic được thực hiện theo cùng một thứ tự trong cả hai phiên? Giao dịch có được 'mở' trong một thời gian dài không? Liệu khóa xảy ra trong thời gian người dùng nghĩ về thời gian hoặc trong khi thực hiện giao dịch đang hoạt động?
ik_zelf

@Sandy Tôi đã cập nhật câu hỏi
Sathyajith Bhat

@ik_zelf Tôi đã cập nhật câu hỏi
Sathyajith Bhat

1
Tôi không rõ tại sao đây là một vấn đề - Oracle đang làm chính xác những gì nó phải làm, đó là tuần tự hóa truy cập vào một hàng. Nếu ai đó có hàng đó, bạn có thể đọc phiên bản trước của nó, nhưng để viết bạn phải đợi họ phát hành khóa. Các chỉ "sửa chữa" cho nó là một trong hai a) không lãng phí thời gian và COMMIThay ROLLBACKtrong một thời gian hợp lý hoặc b) sắp xếp như vậy mà mọi người cùng không phải lúc nào muốn cùng hàng cùng một lúc.
Gaius

Câu trả lời:


10

Có thể tìm ra dòng nào của một thủ tục được lưu trữ đang gây ra những tranh chấp khóa hàng này không?

Không chính xác nhưng bạn có thể nhận được câu lệnh SQL gây ra khóa và lần lượt xác định các dòng liên quan trong thủ tục.

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

Điều gì sẽ là hướng dẫn chung để giảm / tránh / loại bỏ các vấn đề như vậy với mã hóa?

Phần Hướng dẫn về khái niệm của Oracle về các khóa nói: "Một hàng chỉ bị khóa khi được sửa đổi bởi một nhà văn." Một phiên khác cập nhật cùng một hàng sau đó sẽ đợi phiên đầu tiên đến COMMIThoặc ROLLBACKtrước khi nó có thể tiếp tục. Để loại bỏ vấn đề bạn có thể tuần tự hóa người dùng, nhưng đây là một số điều có thể làm giảm vấn đề có lẽ đến mức không phải là vấn đề.

  • COMMITthường xuyên hơn Mọi COMMITbản phát hành đều khóa, vì vậy nếu bạn có thể thực hiện cập nhật theo đợt thì khả năng phiên khác cần cùng hàng sẽ giảm.
  • Đảm bảo bạn không cập nhật bất kỳ hàng nào mà không thay đổi giá trị của chúng. Ví dụ, UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);nên được viết lại dưới dạng chọn lọc hơn (đọc ít khóa hơn) UPDATE t1 SET f1=f1+1 WHERE f2=’a’;. Tất nhiên, nếu thay đổi câu lệnh sẽ vẫn khóa phần lớn các hàng trong bảng thì thay đổi sẽ chỉ có lợi ích dễ đọc.
  • Hãy chắc chắn rằng bạn đang sử dụng các chuỗi thay vì khóa một bảng để thêm một bảng vào giá trị hiện tại cao nhất.
  • Hãy chắc chắn rằng bạn không sử dụng chức năng khiến chỉ mục không được sử dụng. Nếu chức năng là cần thiết xem xét làm cho nó một chỉ số dựa trên chức năng.
  • Suy nghĩ theo bộ. Xem xét liệu một vòng lặp chạy một khối PL / SQL thực hiện các bản cập nhật có thể được viết lại dưới dạng một câu lệnh cập nhật không. Nếu không thì có lẽ xử lý hàng loạt có thể được sử dụng với BULK COLLECT ... FORALL.
  • Giảm công việc được thực hiện giữa lần đầu tiên UPDATECOMMIT. Ví dụ: nếu mã gửi email sau mỗi lần cập nhật, hãy xem xét việc xếp hàng email và gửi chúng sau khi thực hiện cập nhật.
  • Thiết kế ứng dụng để xử lý chờ bằng cách thực hiện một SELECT ... FOR UPDATE NOWAIThoặc WAIT 2. Sau đó, bạn có thể bắt gặp việc không thể khóa hàng và thông báo cho người dùng rằng một phiên khác đang sửa đổi cùng một dữ liệu.

7

Tôi sẽ cung cấp một câu trả lời từ quan điểm của nhà phát triển.

Theo tôi, khi bạn gặp phải một cuộc tranh chấp hàng như ví dụ bạn mô tả, đó là do bạn có lỗi trong ứng dụng của mình. Trong hầu hết các trường hợp, kiểu tranh chấp này là dấu hiệu của lỗ hổng cập nhật bị mất. Chủ đề này trên AskTom giải thích khái niệm về một bản cập nhật bị mất:

Một bản cập nhật bị mất xảy ra khi:

phiên 1: đọc hồ sơ nhân viên của Tom

phiên 2: đọc hồ sơ nhân viên của Tom

phiên 1: cập nhật hồ sơ nhân viên của Tom

phiên 2: cập nhật hồ sơ nhân viên của Tom

Phần 2 sẽ QUÁ NHIỀU thay đổi của phiên 1 mà không bao giờ thấy chúng - dẫn đến cập nhật bị mất.

Bạn đã trải qua một tác dụng phụ khó chịu của bản cập nhật bị mất: phiên 2 có thể bị chặn do phiên 1 chưa được cam kết. Tuy nhiên, vấn đề chính là phiên 2 cập nhật hồ sơ một cách mù quáng. Giả sử rằng cả hai phiên đưa ra tuyên bố:

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

Sau cả hai câu lệnh, các sửa đổi của session1 đã bị ghi đè, mà không có phiên 2 nào được thông báo rằng hàng đã được sửa đổi bởi phiên 1.


Mất cập nhật (và hiệu ứng phụ tranh chấp) sẽ không bao giờ xảy ra, chúng có thể tránh được 100%. Bạn nên sử dụng khóa để ngăn chặn chúng bằng hai phương pháp chính: khóa lạc quan và bi quan .

1) Khóa bi quan

Bạn muốn cập nhật một hàng. Trong chế độ này, bạn sẽ ngăn người khác sửa đổi hàng này bằng cách yêu cầu khóa trên hàng đó ( SELECT ... FOR UPDATE NOWAITcâu lệnh). Nếu hàng đã được sửa đổi, bạn sẽ nhận được thông báo lỗi, bạn có thể dịch một cách duyên dáng cho người dùng cuối (hàng này đang được người dùng khác sửa đổi). Nếu hàng có sẵn, hãy thực hiện các sửa đổi của bạn (CẬP NHẬT), sau đó cam kết bất cứ khi nào giao dịch của bạn hoàn tất.

2) Khóa lạc quan

Bạn muốn cập nhật một hàng. Tuy nhiên, bạn không muốn duy trì khóa trên hàng đó, có lẽ vì bạn sử dụng một số giao dịch để cập nhật hàng (ứng dụng không trạng thái dựa trên web) hoặc có lẽ bạn không muốn bất kỳ người dùng nào giữ khóa quá lâu ( có thể dẫn đến việc người khác bị chặn). Trong trường hợp đó, bạn sẽ không yêu cầu khóa ngay lập tức. Bạn sẽ sử dụng điểm đánh dấu để đảm bảo rằng hàng không thay đổi khi bản cập nhật của bạn được phát hành. Bạn có thể lưu trữ giá trị của tất cả các cột hoặc bạn có thể sử dụng cột dấu thời gian được cập nhật tự động hoặc cột dựa trên chuỗi. Dù lựa chọn của bạn là gì, khi bạn chuẩn bị thực hiện cập nhật của mình, bạn sẽ đảm bảo rằng điểm đánh dấu trên hàng đó không thay đổi bằng cách đưa ra một truy vấn như:

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

Nếu truy vấn trả về một hàng, hãy cập nhật. Nếu không, điều này có nghĩa là ai đó đã sửa đổi hàng kể từ lần cuối bạn truy vấn nó. Bạn sẽ phải khởi động lại quá trình từ đầu.

Lưu ý: Nếu bạn hoàn toàn tin tưởng vào tất cả các ứng dụng truy cập DB của mình, bạn có thể dựa vào bản cập nhật trực tiếp để khóa tối ưu. Bạn có thể phát hành trực tiếp:

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

Nếu câu lệnh cập nhật không có hàng, bạn biết rằng ai đó đã thay đổi hàng này và bạn cần bắt đầu lại tất cả.

Nếu tất cả các ứng dụng đồng ý với chương trình này, bạn sẽ không bao giờ bị chặn bởi người khác và bạn sẽ tránh được cập nhật mù. Tuy nhiên, nếu bạn không khóa hàng trước, bạn vẫn dễ bị khóa vô thời hạn nếu một ứng dụng khác, công việc hàng loạt hoặc cập nhật trực tiếp không thực hiện khóa tối ưu. Đây là lý do tại sao tôi khuyên bạn luôn luôn khóa hàng, bất kể lựa chọn lược đồ khóa nào của bạn (lần nhấn hiệu suất có thể không đáng kể vì bạn truy xuất tất cả các giá trị bao gồm cả hàng khi bạn khóa hàng).

TL; DR

  • Cập nhật một hàng mà không có khóa trên đó trước đó sẽ khiến ứng dụng bị "đóng băng" tiềm năng. Điều này có thể tránh được nếu tất cả các DML tới DB thực hiện khóa lạc quan hoặc bi quan.
  • Xác minh rằng các giá trị trả về của câu lệnh SELECT phù hợp với mọi CHỌN trước đó (để tránh mọi sự cố cập nhật bị mất)

5

Câu trả lời này có thể đủ điều kiện cho một mục trong The Daily WTF.

Phải, sau khi truy tìm các phiên và tìm kiếm thông qua USER_SOURCE- Tôi đã tìm ra nguyên nhân gốc

  • Nguyên nhân, không ngạc nhiên là logic thiếu sót
  • Gần đây, một tuyên bố cập nhật đã được thêm vào SP. Báo cáo cập nhật về cơ bản sẽ cập nhật toàn bộ bảng. Rõ ràng là nhà phát triển trong câu hỏi đã quên về việc thêm quyền vào các mệnh đề để cập nhật các câu lệnh được yêu cầu.
  • Bảng đang được cập nhật như đã đề cập ở trên, một trong những bảng được giao dịch nhiều nhất và có số lượng lớn các bản ghi. Việc cập nhật sẽ mất nhiều thời gian, đau đớn.
  • Kết quả là các phiên khác không thể có được một khóa trên bàn và sẽ ngồi trong các cuộc tranh chấp khóa hàng.
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.