2008-11-03 27 views
14

Hãy nói rằng tôi có một thủ tục lưu trữ đơn giản mà trông như thế này (lưu ý: đây chỉ là một ví dụ, không phải là một thủ tục thực tế):Thủ tục lưu trữ T-SQL có thực thi 'nguyên tử' không?

CREATE PROCEDURE incrementCounter AS 

DECLARE @current int 
SET @current = (select CounterColumn from MyTable) + 1 

UPDATE 
    MyTable 
SET 
    CounterColumn = current 
GO 

Chúng tôi giả sử tôi có một bảng gọi là 'myTable' có chứa một hàng, với 'CounterColumn' chứa số đếm hiện tại của chúng tôi.

Quy trình được lưu trữ này có thể được thực hiện nhiều lần, cùng một lúc không?

tức là điều này có thể:

Tôi gọi 'incrementCounter' hai lần. Gọi A đến điểm mà nó đặt biến 'hiện tại' (giả sử nó là 5). Gọi B đến điểm mà nó đặt biến 'hiện tại' (cũng sẽ là 5). Gọi A kết thúc thực hiện, sau đó gọi B kết thúc. Cuối cùng, bảng phải chứa giá trị là 6, nhưng thay vì chứa 5 do chồng chéo thực thi

+3

Hiện tượng này được gọi là "mất cập nhật". Đó là "cô lập" không phải là nguyên tử mà bạn cần phải nhìn vào. Theo mặc định đọc mức cô lập cam kết nó ** là ** có thể trái với hàm ý trong câu trả lời được chấp nhận. –

+0

Một câu trả lời thực tế cho điều này trong SQL Server 2005 là: Sử dụng một bảng có cột 'IDENTITY', Chèn vào bảng rồi đọc lại giá trị mới bằng' SCOPE_IDENTITY'. Sau đó, bạn không bao giờ có một vụ va chạm. –

Trả lời

12

Điều này dành cho SQL Server.

Mỗi tuyên bố là nguyên tử, nhưng nếu bạn muốn các thủ tục lưu trữ được nguyên tử (hoặc bất kỳ chuỗi các câu lệnh nói chung), bạn cần phải bao quanh một cách rõ ràng những điều khoản với

BEGIN TRANSACTION
Tuyên Bố ...
Tuyên Bố ...
COMMIT GIAO DỊCH

(Nó thường được sử dụng BEGIN TRAN và END TRẦN tắt.)

Tất nhiên có rất nhiều cách để gặp rắc rối khóa tùy thuộc vào những gì khác đang diễn ra cùng một lúc, vì vậy bạn có thể cần một chiến lược để xử lý các giao dịch không thành công. (Một cuộc thảo luận đầy đủ về tất cả các trường hợp có thể dẫn đến ổ khóa, cho dù bạn có sử dụng SP đặc biệt này, vượt quá phạm vi của câu hỏi.) Nhưng chúng vẫn sẽ được tái phân phối vì nguyên tử. Và theo kinh nghiệm của tôi, bạn có thể sẽ ổn, không biết về khối lượng giao dịch của bạn và các hoạt động khác trên cơ sở dữ liệu. Xin lỗi vì đã nói rõ ràng.

Trái ngược với quan niệm sai lầm phổ biến, điều này sẽ hoạt động trong trường hợp của bạn với cài đặt cấp độ giao dịch mặc định.

+2

Nguyên tử không phải là cách chữa trị cho các vấn đề tương tranh và điều kiện chủng tộc. Việc sửa đổi quy trình kiểm tra OP được lưu trữ như bạn đã đề xuất sẽ KHÔNG chấp nhận đồng thời bởi vì các khóa được mua lại và leo thang theo thời gian. Nếu hai khách hàng, A & B chạy thủ tục này cùng một lúc, bạn có thể nhận được mẫu này: A được đọc khóa và đọc bảng, sau đó phát hành khóa. B được đọc khóa và đọc ** cùng một giá trị từ bảng **, sau đó phát hành khóa. A được cập nhật khóa và cập nhật, sau đó B thực hiện tương tự. BLAM. Mày tiêu rồi. Bạn phải có được khóa chính xác trước đó để chặn đúng cách! – ErikE

+0

Như OP nói, đó là một câu hỏi lý thuyết, không phải là một thủ tục thực tế; vì vậy tôi đã trả lời theo cách riêng của mình. Có nhiều sai với mẫu này hơn bạn đề cập, nhưng đó không phải là câu hỏi. Tôi đã không nhận được vào làm thế nào để làm cho điều này hữu ích bởi vì chúng ta không nên đi ở đây ở nơi đầu tiên. Nếu bạn có câu trả lời hay hơn, hãy thêm nó! – dkretz

+3

Ông đặc biệt nói rằng ông muốn chữa bệnh. Câu trả lời của bạn không chữa được. Câu trả lời sai đáng tin cậy cho đến khi họ nhìn sai, đủ để người khác không bị lừa dối. Lý thuyết hay không, nó không đầy đủ. – ErikE

12

Ngoài việc đặt mã giữa BEGIN TRANSACTIONEND TRANSACTION, bạn cần đảm bảo rằng mức cách ly giao dịch của bạn được đặt chính xác.

Ví dụ: SERIALIZABLE mức cách ly sẽ ngăn cập nhật bị mất khi mã chạy đồng thời, nhưng READ COMMITTED (mặc định trong SQL Server Management Studio) sẽ không.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

Như những người khác đã đề cập, trong khi đảm bảo tính nhất quán, điều này có thể gây tắc nghẽn và bế tắc và vì vậy có thể không phải là giải pháp tốt nhất trong thực tế.

+0

'SERIALIZABLE' và' REPEATABLE READ' sẽ ngăn chặn điều này nhưng giải thích của bạn là tại sao [không chính xác] (http: // dba.stackexchange.com/a/12718/3690) –

+2

@Dave, tôi đã nghiên cứu thêm một chút và nhận thấy rằng mô tả của bạn không chính xác. Mức cô lập SERIALIZABLE sẽ không ngăn các khách hàng khác đọc cùng một giá trị. Những gì nó S do làm nếu hai khách hàng đọc trong SERIALIZABLE sau đó một cố gắng cập nhật là gây ra một bế tắc và một kết nối khách hàng sẽ được chấm dứt với thành kiến. – ErikE

+0

Nó cũng làm chậm mọi thứ - rất nhiều. – dkretz

0

Có lẽ tôi đang đọc quá nhiều vào ví dụ của bạn (và tình hình thực tế của bạn có thể phức tạp hơn), nhưng tại sao bạn không làm điều này trong một tuyên bố?

CREATE PROCEDURE incrementCounter AS 

UPDATE 
    MyTable 
SET 
    CounterColumn = CounterColumn + 1 

GO 

Bằng cách đó, nó sẽ tự động nguyên tử và nếu hai bản cập nhật được executued cùng một lúc, họ sẽ luôn luôn được sắp xếp theo SQL Server để tránh cuộc xung đột mà bạn mô tả.Tuy nhiên, nếu tình hình thực tế của bạn phức tạp hơn nhiều, thì việc gói nó trong một giao dịch là cách tốt nhất để làm điều này. Tuy nhiên, nếu một quy trình khác đã cho phép mức độc lập "kém an toàn" (như một lần cho phép đọc bẩn hoặc đọc không lặp lại), thì tôi không nghĩ rằng giao dịch sẽ bảo vệ chống lại điều này, như một quá trình khác có thể thấy vào dữ liệu được cập nhật một phần nếu dữ liệu được chọn để cho phép đọc không an toàn.

+1

Ông có thể cần giá trị cũ của cột truy cập, trong trường hợp này, ông vẫn có thể thực hiện phương pháp này và nhận được nó bằng cách sử dụng mệnh đề OUTPUT. –

+0

Một cái gì đó về việc cần giá trị vừa cập nhật ... – ErikE

1

tôi sử dụng phương pháp này

CREATE PROCEDURE incrementCounter 
AS 

DECLARE @current int 

UPDATE MyTable 
SET 
    @current = CounterColumn = CounterColumn + 1 

Return @current 

thủ tục này làm tất cả hai lệnh cùng một lúc và nó là cô lập từ giao dịch khác.

+0

Bạn không chắc chắn nó hoạt động. Có thể nó. Là một tuyên bố đơn lẻ rõ ràng là không đủ: http://sqlperformance.com/2014/07/t-sql-queries/isolation-levels –

0

Câu trả lời ngắn cho câu hỏi của bạn là CÓ và có thể sẽ xuất hiện ngắn. Nếu bạn muốn chặn thực thi đồng thời các thủ tục đã lưu, hãy bắt đầu một giao dịch và cập nhật cùng một dữ liệu trong mỗi lần thực hiện thủ tục đã lưu trước khi tiếp tục thực hiện bất kỳ công việc nào trong quy trình.

CREATE PROCEDURE .. 
BEGIN TRANSACTION 
UPDATE mylock SET ref = ref + 1 
... 

Điều này sẽ buộc các thực thi đồng thời khác đợi đến lượt họ vì họ sẽ không thể thay đổi giá trị 'ref' cho đến khi hoàn thành giao dịch và khóa cập nhật liên quan.

Nói chung, tốt hơn là giả sử kết quả của bất kỳ và tất cả truy vấn SELECT là cũ trước chúng thậm chí đã được thực thi. Sử dụng các mức cô lập "nặng" để giải quyết tình trạng không may này làm hạn chế khả năng mở rộng. Tốt hơn nhiều để thay đổi cấu trúc theo cách đưa ra các giả định lạc quan về trạng thái của hệ thống mà bạn mong đợi tồn tại trong quá trình cập nhật để khi giả định của bạn thất bại, bạn có thể thử lại sau và hy vọng có kết quả tốt hơn. Ví dụ:

UPDATE 
    MyTable 
SET 
    CounterColumn = current 
WHERE CounterColumn = current - 1 

Sử dụng ví dụ của bạn có thêm mệnh đề WHERE cập nhật này không ảnh hưởng đến bất kỳ hàng nào nếu giả định trạng thái hiện tại không thành công. Kiểm tra @@ ROWCOUNT để kiểm tra số hàng và rollback hoặc một số hành động khác khi thích hợp trong khi nó khác với kết quả mong đợi.

Các vấn đề liên quan