Tôi có nên cam kết hoặc khôi phục một giao dịch đã đọc không?


95

Tôi có một truy vấn đọc mà tôi thực hiện trong một giao dịch để tôi có thể chỉ định mức cô lập. Sau khi hoàn tất truy vấn, tôi nên làm gì?

  • Cam kết giao dịch
  • Khôi phục giao dịch
  • Không làm gì (điều này sẽ khiến giao dịch được khôi phục lại ở cuối khối đang sử dụng)

Ý nghĩa của việc làm mỗi việc là gì?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

CHỈNH SỬA: Câu hỏi không phải là liệu có nên sử dụng giao dịch hay không hoặc có những cách nào khác để đặt mức giao dịch. Câu hỏi là nếu nó tạo ra bất kỳ sự khác biệt nào mà một giao dịch không sửa đổi bất kỳ điều gì được cam kết hoặc quay trở lại. Có sự khác biệt về hiệu suất không? Nó có ảnh hưởng đến các kết nối khác không? Bất kỳ sự khác biệt nào khác?


1
Có thể bạn đã biết về điều này, nhưng với ví dụ bạn đã cung cấp, bạn có thể có kết quả tương đương bằng cách thực hiện đơn giản truy vấn: SELECT * FROM SomeTable with NOLOCK
JasonTrue

@Stefan, có vẻ như hầu hết chúng ta đều thắc mắc tại sao bạn lại bận tâm giao dịch trên một thao tác chỉ đọc. Bạn có thể cho chúng tôi biết nếu bạn biết về NOLOCK và nếu bạn biết, tại sao bạn không đi theo con đường đó.
StingyJack

Tôi biết về NOLOCK, nhưng hệ thống này hoạt động dựa trên các cơ sở dữ liệu khác nhau cũng như SQL Server, vì vậy tôi đang cố gắng tránh các gợi ý khóa cụ thể của SQL Server. Đây là một câu hỏi gây tò mò hơn bất cứ điều gì khác vì ứng dụng đang hoạt động tốt với đoạn mã trên.
Stefan Moser

À, trong trường hợp đó, tôi đang xóa thẻ sqlserver, vì thẻ đó biểu thị MSSqlServer là sản phẩm mục tiêu.
StingyJack

@StingyJack - Bạn nói đúng, tôi không nên sử dụng thẻ sqlserver.
Stefan Moser

Câu trả lời:


51

Bạn cam kết. Giai đoạn = Stage. Không có sự thay thế hợp lý nào khác. Nếu bạn đã bắt đầu một giao dịch, bạn nên đóng nó. Cam kết phát hành bất kỳ khóa nào bạn có thể đã có và cũng hợp lý như nhau với các mức cách ly ReadUncommiss hoặc Serializable. Việc dựa vào khôi phục ngầm định - trong khi có lẽ tương đương về mặt kỹ thuật - chỉ là hình thức kém.

Nếu điều đó chưa thuyết phục được bạn, hãy tưởng tượng người tiếp theo chèn một câu lệnh cập nhật vào giữa mã của bạn và phải theo dõi quá trình khôi phục ngầm xảy ra và xóa dữ liệu của anh ta.


44
Có một giải pháp thay thế hợp lý - khôi phục. Phục hồi rõ ràng, đó là. Nếu bạn không có ý định thay đổi bất kỳ điều gì, thì việc khôi phục đảm bảo mọi thứ sẽ được hoàn tác. Tất nhiên, không nên có bất kỳ thay đổi nào; rollback đảm bảo rằng.
Jonathan Leffler

2
Các DBMS khác nhau có thể có ngữ nghĩa 'hoàn thành giao dịch ngầm định' khác nhau. IBM Informix (và tôi tin rằng DB2) thực hiện khôi phục ngầm; theo tin đồn, Oracle thực hiện một cam kết ngầm. Tôi thích khôi phục ngầm.
Jonathan Leffler

8
Giả sử tôi tạo một bảng tạm thời, điền nó với id, nối nó với một bảng dữ liệu để chọn dữ liệu đi với id, sau đó xóa bảng tạm thời. Tôi thực sự chỉ đang đọc dữ liệu và tôi không quan tâm điều gì xảy ra với bảng tạm thời, vì nó là tạm thời ... nhưng từ góc độ hiệu suất, sẽ tốn kém hơn khi khôi phục giao dịch hoặc cam kết nó? Tác dụng của commit / rollback là gì khi không có gì ngoại trừ bảng tạm thời và các hoạt động đọc có liên quan?
Triynko

4
@Triynko - Theo trực giác, tôi đoán rằng ROLLBACK đắt hơn. COMMIT là trường hợp sử dụng bình thường và ROLLBACK trường hợp đặc biệt. Nhưng, ngoại trừ học thuật, ai quan tâm? Tôi chắc chắn rằng có 1000 điểm tối ưu hóa tốt hơn cho ứng dụng của bạn. Nếu bạn thực sự tò mò, bạn có thể tìm mã xử lý giao dịch mySQL tại bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…
Mark Brackett

3
@Triynko - Cách duy nhất để tối ưu hóa là lập hồ sơ. Đó là một sự thay đổi mã đơn giản như vậy, không có lý do gì để không lập hồ sơ cả hai phương pháp nếu bạn thực sự muốn tối ưu hóa nó. Đảm bảo cập nhật kết quả cho chúng tôi!
Mark Brackett

28

Nếu bạn không thay đổi bất kỳ điều gì, thì bạn có thể sử dụng COMMIT hoặc ROLLBACK. Một trong hai sẽ giải phóng bất kỳ khóa đọc nào mà bạn đã có được và vì bạn chưa thực hiện bất kỳ thay đổi nào khác nên chúng sẽ tương đương.


2
Cảm ơn đã cho tôi biết chúng tương đương nhau. Theo tôi, điều này trả lời tốt nhất cho câu hỏi thực tế.
chowey

nó sẽ cung cấp cho giao dịch không hoạt động nếu chúng tôi sử dụng cam kết mà không có cập nhật thực tế. tôi vừa đối mặt với nó trên trang web trực tiếp của mình
Muhammad Omer Aslam

6

Nếu bạn bắt đầu một giao dịch, thì phương pháp tốt nhất là luôn luôn cam kết nó. Nếu một ngoại lệ được đưa ra bên trong khối sử dụng (giao dịch) của bạn, giao dịch sẽ tự động được khôi phục.


3

IMHO có thể hợp lý khi gói các truy vấn chỉ đọc trong các giao dịch vì (đặc biệt là trong Java) bạn có thể yêu cầu giao dịch là "chỉ đọc", do đó trình điều khiển JDBC có thể xem xét việc tối ưu hóa truy vấn (nhưng không bắt buộc phải làm vậy INSERTTuy nhiên sẽ ngăn bạn phát hành ). Ví dụ: trình điều khiển Oracle sẽ tránh hoàn toàn việc khóa bảng trên các truy vấn trong giao dịch được đánh dấu là chỉ đọc, điều này đạt được rất nhiều hiệu suất trên các ứng dụng hướng đọc nhiều.


3

Xem xét các giao dịch lồng nhau .

Hầu hết các RDBMS không hỗ trợ các giao dịch lồng nhau hoặc cố gắng mô phỏng chúng một cách rất hạn chế.

Ví dụ, trong MS SQL Server, việc khôi phục trong một giao dịch bên trong (không phải là một giao dịch thực, MS SQL Server chỉ tính các mức giao dịch!) Sẽ khôi phục tất cả mọi thứ đã xảy ra trong giao dịch ngoài cùng (đó là giao dịch thực).

Một số trình bao bọc cơ sở dữ liệu có thể coi việc khôi phục trong một giao dịch bên trong là một dấu hiệu cho thấy lỗi đã xảy ra và khôi phục mọi thứ trong giao dịch ngoài cùng, bất kể giao dịch ngoài cùng được cam kết hay quay trở lại.

Vì vậy, COMMIT là cách an toàn, khi bạn không thể loại trừ rằng thành phần của bạn được sử dụng bởi một số mô-đun phần mềm.

Xin lưu ý rằng đây là câu trả lời chung cho câu hỏi. Ví dụ mã giải quyết vấn đề một cách khéo léo với một giao dịch bên ngoài bằng cách mở một kết nối cơ sở dữ liệu mới.

Về hiệu suất: tùy thuộc vào mức độ cô lập, các CHỌN có thể yêu cầu mức độ KHÓA khác nhau và dữ liệu tạm thời (ảnh chụp nhanh). Điều này được làm sạch khi giao dịch đóng. Không quan trọng việc này được thực hiện thông qua COMMIT hay ROLLBACK. Có thể có sự khác biệt không đáng kể về thời gian sử dụng CPU - một COMMIT có thể phân tích cú pháp nhanh hơn ROLLBACK (ít hơn hai ký tự) và những khác biệt nhỏ khác. Rõ ràng, điều này chỉ đúng với các hoạt động chỉ đọc!

Hoàn toàn không được yêu cầu: một lập trình viên khác có thể đọc mã có thể cho rằng ROLLBACK ngụ ý một điều kiện lỗi.


2

Chỉ là một lưu ý phụ, nhưng bạn cũng có thể viết mã đó như thế này:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

Và nếu bạn cấu trúc lại mọi thứ một chút, bạn cũng có thể di chuyển khối sử dụng cho IDataReader lên trên cùng.


1

Nếu bạn đặt SQL vào một thủ tục được lưu trữ và thêm điều này vào phía trên truy vấn:

set transaction isolation level read uncommitted

thì bạn không phải nhảy qua bất kỳ vòng lặp nào trong mã C #. Đặt mức cách ly giao dịch trong một quy trình được lưu trữ không khiến cài đặt áp dụng cho tất cả các lần sử dụng kết nối đó trong tương lai (đó là điều bạn phải lo lắng với các cài đặt khác vì các kết nối được gộp chung). Vào cuối thủ tục được lưu trữ, nó sẽ quay trở lại bất cứ thứ gì mà kết nối đã được khởi tạo.


1

ROLLBACK chủ yếu được sử dụng trong trường hợp có lỗi hoặc các trường hợp ngoại lệ, và COMMIT trong trường hợp hoàn thành thành công.

Chúng ta nên đóng các giao dịch bằng COMMIT (nếu thành công) và ROLLBACK (nếu không thành công), ngay cả trong trường hợp giao dịch chỉ đọc mà điều đó dường như không quan trọng. Trên thực tế, nó rất quan trọng, đối với tính nhất quán và khả năng chống lại tương lai.

Một giao dịch chỉ đọc về mặt logic có thể "thất bại" theo nhiều cách, ví dụ:

  • một truy vấn không trả về chính xác một hàng như mong đợi
  • một thủ tục được lưu trữ tạo ra một ngoại lệ
  • dữ liệu đã tìm nạp được phát hiện là không nhất quán
  • người dùng hủy giao dịch vì mất quá nhiều thời gian
  • bế tắc hoặc hết thời gian

Nếu COMMIT và ROLLBACK được sử dụng đúng cách cho một giao dịch chỉ đọc, nó sẽ tiếp tục hoạt động như mong đợi nếu mã ghi DB được thêm vào một thời điểm nào đó, ví dụ như cho bộ nhớ đệm, kiểm tra hoặc thống kê.

ROLLBACK ngầm định chỉ được sử dụng cho các tình huống "lỗi nghiêm trọng", khi ứng dụng bị treo hoặc thoát với lỗi không thể khôi phục, lỗi mạng, mất nguồn, v.v.


0

Cho rằng một READ không thay đổi trạng thái, tôi sẽ không làm gì cả. Thực hiện một cam kết sẽ không làm gì cả, ngoại trừ lãng phí một chu kỳ để gửi yêu cầu đến cơ sở dữ liệu. Bạn chưa thực hiện một thao tác đã thay đổi trạng thái. Tương tự như vậy đối với khôi phục.

Tuy nhiên, bạn nên đảm bảo dọn dẹp các đối tượng của mình và đóng các kết nối với cơ sở dữ liệu. Không đóng các kết nối của bạn có thể dẫn đến sự cố nếu mã này được gọi nhiều lần.


3
Tùy thuộc vào mức độ cô lập, một lựa chọn CÓ THỂ lấy được khóa sẽ chặn các giao dịch khác.
Graeme Perrow

Kết nối sẽ bị đóng ở cuối khối đang sử dụng - đó là những gì nó ở đó. Nhưng điểm tốt là lưu lượng mạng có lẽ là phần chậm nhất của phương trình.
Joel Coehoorn

1
Giao dịch sẽ được cam kết hoặc quay trở lại theo cách này hay cách khác, vì vậy phương pháp tốt nhất là luôn đưa ra một cam kết nếu nó thành công.
Neil Barnwell

0

Nếu bạn đặt AutoCommit false, thì CÓ.

Trong một thử nghiệm với JDBC (trình điều khiển Postgresql), tôi thấy rằng nếu truy vấn chọn ngắt (do hết thời gian chờ), thì bạn không thể bắt đầu truy vấn chọn mới trừ khi bạn khôi phục.


-2

Trong mẫu mã của bạn, nơi bạn có

  1. // Làm gì đó hữu ích

    Bạn có đang thực thi Câu lệnh SQL thay đổi dữ liệu không?

Nếu không, không có cái gọi là Giao dịch "Đọc" ... Chỉ những thay đổi từ một Câu lệnh Chèn, Cập nhật và Xóa (câu lệnh có thể thay đổi dữ liệu) trong một Giao dịch ... Những gì bạn đang nói là khóa SQL Máy chủ đặt dữ liệu bạn đang đọc vì các giao dịch KHÁC ảnh hưởng đến dữ liệu đó. Mức độ của các khóa này phụ thuộc vào Mức cách ly của máy chủ SQL.

Nhưng bạn không thể Cam kết hoặc Quay lại bất kỳ điều gì, nếu câu lệnh SQL của bạn không thay đổi bất kỳ điều gì.

Nếu bạn đang thay đổi dữ liệu, thì bạn có thể thay đổi mức cách ly mà không cần bắt đầu chuyển đổi một cách rõ ràng ... Mọi Câu lệnh SQL riêng lẻ đều ngầm định trong một giao dịch. Việc bắt đầu một Giao dịch một cách rõ ràng chỉ cần thiết để đảm bảo rằng 2 hoặc nhiều câu lệnh nằm trong cùng một giao dịch.

Nếu tất cả những gì bạn muốn làm là đặt mức cô lập giao dịch, thì chỉ cần đặt CommandText của lệnh thành "Đặt đọc lặp lại mức cô lập giao dịch" (hoặc bất kỳ mức nào bạn muốn), đặt CommandType thành CommandType.Text và thực hiện lệnh. (bạn có thể sử dụng Command.ExecuteNonQuery ())

LƯU Ý: Nếu bạn đang thực hiện NHIỀU câu lệnh đọc và muốn tất cả chúng "nhìn thấy" cùng trạng thái của cơ sở dữ liệu như câu lệnh đầu tiên, thì bạn cần đặt mức cô lập trên cùng Đọc lặp lại hoặc Có thể nối tiếp ...


// Làm điều gì đó hữu ích không thay đổi bất kỳ dữ liệu nào, chỉ cần đọc. Tất cả những gì tôi muốn làm là chỉ định mức cô lập của truy vấn.
Stefan Moser

Sau đó, bạn có thể làm điều đó mà không cần bắt đầu giao dịch từ máy khách một cách rõ ràng ... Chỉ cần thực hiện chuỗi sql "Đặt mức cách ly giao dịch ReadUncomiled", "... Đã đọc cam kết", "... Lặp lại đọc", "... Ảnh chụp nhanh" hoặc "... Serializable" "Đã cam kết đọc mức độ cô lập"
Charles Bretana

3
Các giao dịch vẫn quan trọng ngay cả khi bạn chỉ đọc. Nếu bạn muốn thực hiện một số thao tác đọc, thực hiện chúng bên trong giao dịch sẽ đảm bảo tính nhất quán. Làm chúng mà không có ai sẽ không.
MarkR

vâng, xin lỗi, bạn nói đúng, ít nhất điều này đúng nếu mức Cách ly được đặt thành Đọc lặp lại hoặc cao hơn.
Charles Bretana

-3

Bạn có cần chặn người khác đọc cùng một dữ liệu không? Tại sao sử dụng một giao dịch?

@Joel - Câu hỏi của tôi sẽ được diễn đạt tốt hơn là "Tại sao sử dụng giao dịch trên truy vấn đã đọc?"

@Stefan - Nếu bạn định sử dụng AdHoc SQL chứ không phải proc được lưu trữ, thì chỉ cần thêm WITH (NOLOCK) sau các bảng trong truy vấn. Bằng cách này, bạn không phải chịu chi phí (mặc dù tối thiểu) trong ứng dụng và cơ sở dữ liệu cho một giao dịch.

SELECT * FROM SomeTable WITH (NOLOCK)

CHỈNH SỬA @ Nhận xét 3: Vì bạn đã có "sqlserver" trong các thẻ câu hỏi, tôi đã cho rằng MSSQLServer là sản phẩm mục tiêu. Bây giờ điểm đó đã được làm rõ, tôi đã chỉnh sửa các thẻ để xóa tham chiếu sản phẩm cụ thể.

Tôi vẫn không chắc chắn về lý do tại sao bạn muốn thực hiện giao dịch trên tùy chọn đọc ngay từ đầu.


1
Đến mức cô lập đã đặt cùng một lúc. Bạn có thể sử dụng giao dịch để thực sự giảm số lượng khóa cho truy vấn.
Joel Coehoorn

1
Tôi đang sử dụng giao dịch để có thể sử dụng mức cô lập thấp hơn và giảm khóa.
Stefan Moser

@StingyJack - Mã này có thể thực thi trên một số cơ sở dữ liệu khác nhau, vì vậy NOLOCK không phải là một tùy chọn.
Stefan Moser
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.