Là giao dịch rõ ràng cần thiết trong vòng lặp while này?


11

Máy chủ SQL 2014:

Chúng tôi có một bảng rất lớn (hàng 100 triệu) và chúng tôi cần cập nhật một vài trường trên đó.

Đối với vận chuyển gỗ, vv, rõ ràng, chúng tôi cũng muốn giữ nó cho các giao dịch có quy mô lớn.

Nếu chúng ta để bên dưới chạy một chút, sau đó hủy / chấm dứt truy vấn, liệu công việc đã hoàn thành cho đến nay có được cam kết hay chúng ta cần thêm các câu lệnh BEGIN TRANSACTION / END TRANSACTION rõ ràng để chúng ta có thể hủy bất kỳ lúc nào?

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END

Câu trả lời:


13

Các câu lệnh riêng lẻ - DML, DDL, v.v. - là các giao dịch trong chính chúng. Vì vậy, có, sau mỗi lần lặp của vòng lặp (về mặt kỹ thuật: sau mỗi câu lệnh), bất kỳ UPDATEcâu lệnh nào thay đổi đều được tự động cam kết.

Tất nhiên, luôn có một ngoại lệ, phải không? Có thể kích hoạt Giao dịch tiềm ẩn thông qua SET IMPLICIT_TRANSACTIONS , trong trường hợp đó, UPDATEcâu lệnh đầu tiên sẽ bắt đầu một giao dịch mà bạn sẽ phải COMMIThoặc ROLLBACKcuối cùng. Đây là cài đặt cấp phiên bị TẮT theo mặc định trong hầu hết các trường hợp.

chúng ta có cần thêm các câu lệnh BEGIN TRANSACTION / END TRANSACTION rõ ràng để chúng ta có thể hủy bỏ bất cứ lúc nào không?

Và trên thực tế, do bạn muốn có thể dừng quá trình và khởi động lại, thêm một giao dịch rõ ràng (hoặc kích hoạt Giao dịch ngầm) sẽ là một ý tưởng tồi vì việc dừng quá trình có thể bắt trước khi thực hiện COMMIT. Trong trường hợp đó, bạn sẽ cần phải phát hành thủ công COMMIT(nếu bạn đang ở SSMS) hoặc nếu bạn đang chạy công việc này từ một công việc của Tác nhân SQL, thì bạn không có cơ hội đó và có thể kết thúc bằng một giao dịch mồ côi.


Ngoài ra, bạn có thể muốn đặt @CHUNK_SIZEthành một số nhỏ hơn. Khóa leo thang thường xảy ra ở 5000 khóa có được trên một đối tượng. Tùy thuộc vào kích thước của các hàng và nếu nó đang thực hiện Khóa hàng so với khóa Trang, bạn có thể vượt quá giới hạn đó. Nếu kích thước của một hàng sao cho chỉ có 1 hoặc 2 hàng vừa với mỗi trang, thì bạn có thể luôn nhấn phím này ngay cả khi nó đang thực hiện khóa Trang.

Nếu bảng được phân vùng thì bạn có tùy chọn cài đặt LOCK_ESCALATIONtùy chọn (được giới thiệu trong SQL Server 2008) cho bảng để AUTOnó chỉ khóa phân vùng chứ không phải toàn bộ bảng khi leo thang. Hoặc, đối với bất kỳ bảng nào bạn có thể đặt cùng tùy chọn đó DISABLE, mặc dù bạn sẽ phải rất cẩn thận về điều đó. Xem ALTER TABLE để biết chi tiết.

Dưới đây là một số tài liệu nói về Khóa nâng cấp và các ngưỡng: Khóa nâng cấp (có nghĩa là áp dụng cho "SQL Server 2008 R2 và các phiên bản cao hơn"). Và đây là một bài đăng trên blog liên quan đến việc phát hiện và sửa lỗi leo thang khóa: Khóa trong Microsoft SQL Server (Phần 12 - Khóa thoát hiểm) .


Không liên quan đến câu hỏi chính xác, nhưng liên quan đến truy vấn trong câu hỏi, có một vài cải tiến có thể được thực hiện ở đây (hoặc ít nhất có vẻ như vậy chỉ khi nhìn vào nó):

  1. Đối với vòng lặp của bạn, việc thực hiện WHILE (@@ROWCOUNT = @CHUNK_SIZE)sẽ tốt hơn một chút vì nếu số lượng hàng được cập nhật ở lần lặp cuối cùng ít hơn số lượng được yêu cầu CẬP NHẬT, thì không còn việc phải làm.

  2. Nếu deletedlĩnh vực là một BITkiểu dữ liệu, sau đó không phải là giá trị xác định bằng hay không deletedDate2000-01-01? Tại sao bạn cần cả hai?

  3. Nếu hai trường này là mới và bạn đã thêm chúng vì NULLvậy đó có thể là thao tác trực tuyến / không chặn và hiện muốn cập nhật chúng thành giá trị "mặc định" của chúng, thì điều đó là không cần thiết. Bắt đầu trong SQL Server 2012 (chỉ dành cho Phiên bản doanh nghiệp), việc thêm NOT NULLcác cột có ràng buộc DEFAULT là các hoạt động không chặn miễn là giá trị của DEFAULT là một hằng số. Vì vậy, nếu bạn chưa sử dụng các trường, chỉ cần thả và thêm lại dưới dạng NOT NULLvà với một ràng buộc DEFAULT.

  4. Nếu không có quá trình nào khác đang cập nhật các trường này trong khi bạn đang thực hiện CẬP NHẬT này, thì sẽ nhanh hơn nếu bạn xếp hàng các bản ghi mà bạn muốn cập nhật và sau đó chỉ cần xử lý hàng đợi đó. Có một lần nhấn hiệu năng trong phương thức hiện tại khi bạn phải truy vấn lại bảng mỗi lần để có được bộ cần thay đổi. Thay vào đó, bạn có thể thực hiện các thao tác sau chỉ quét bảng một lần trên hai trường đó và sau đó chỉ phát hành các câu lệnh CẬP NHẬT rất được nhắm mục tiêu. Cũng không có hình phạt nào dừng quá trình bất cứ lúc nào và bắt đầu sau vì dân số ban đầu của hàng đợi sẽ chỉ đơn giản là tìm các bản ghi còn lại để cập nhật.

    1. Tạo một bảng tạm thời (#FullSet) chỉ có các trường chính từ chỉ mục được nhóm trong đó.
    2. Tạo một bảng tạm thời thứ hai (#C Hiện tại) có cùng cấu trúc.
    3. chèn vào #FullSet thông qua SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;

      Cái TOP(n)ở trong đó là do kích thước của cái bàn. Với 100 triệu hàng trong bảng, bạn thực sự không cần phải điền vào bảng xếp hàng với toàn bộ bộ khóa đó, đặc biệt nếu bạn có kế hoạch dừng quá trình thường xuyên và khởi động lại sau. Vì vậy, có thể đặt nthành 1 triệu và để cho đến khi hoàn thành. Bạn luôn có thể lên lịch việc này trong công việc Tác nhân SQL chạy bộ 1 triệu (hoặc thậm chí ít hơn) và sau đó đợi thời gian đã lên lịch tiếp theo để nhận lại. Sau đó, bạn có thể lên lịch để chạy cứ sau 20 phút, do đó sẽ có một số phòng thở được thi hành giữa các bộ n, nhưng nó vẫn sẽ hoàn thành toàn bộ quá trình không giám sát. Sau đó, chỉ cần xóa công việc khi không còn gì để làm :-).

    4. trong một vòng lặp, làm:
      1. Dân số lô hiện tại thông qua một cái gì đó như DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
      2. IF (@@ROWCOUNT = 0) BREAK;
      3. Thực hiện CẬP NHẬT bằng cách sử dụng một cái gì đó như: UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
      4. Xóa bộ hiện tại: TRUNCATE TABLE #CurrentSet;
  5. Trong một số trường hợp, nó giúp thêm Chỉ mục được Lọc để hỗ trợ các SELECTnguồn cấp dữ liệu đó vào #FullSetbảng tạm thời. Dưới đây là một số cân nhắc liên quan đến việc thêm một chỉ mục như vậy:
    1. Điều kiện WHERE phải khớp với điều kiện WHERE của truy vấn của bạn, do đó WHERE deleted is null or deletedDate is null
    2. Khi bắt đầu quá trình, hầu hết các hàng sẽ khớp với điều kiện WHERE của bạn, vì vậy một chỉ mục không hữu ích. Bạn có thể muốn đợi cho đến khi đâu đó khoảng 50% trước khi thêm điều này. Tất nhiên, nó giúp được bao nhiêu và khi nào tốt nhất để thêm chỉ số khác nhau do một số yếu tố, vì vậy đó là một chút thử nghiệm và lỗi.
    3. Bạn có thể phải cập nhật thủ công STATS và / hoặc REBUILD chỉ mục để cập nhật chỉ số vì dữ liệu cơ sở thay đổi khá thường xuyên
    4. Hãy nhớ rằng chỉ số, trong khi giúp SELECT, sẽ làm tổn thương UPDATEvì nó là một đối tượng khác phải được cập nhật trong quá trình hoạt động đó, do đó có thêm I / O. Điều này đóng vai trò cả hai bằng cách sử dụng Chỉ mục được lọc (thu nhỏ khi bạn cập nhật các hàng vì có ít hàng khớp với bộ lọc hơn) và đợi một lát để thêm chỉ mục (nếu ban đầu nó sẽ không hữu ích lắm, thì không có lý do gì để phát sinh I / O bổ sung).

CẬP NHẬT: Vui lòng xem câu trả lời của tôi cho một câu hỏi có liên quan đến câu hỏi này để thực hiện đầy đủ những gì được đề xuất ở trên, bao gồm cơ chế theo dõi trạng thái và hủy sạch: máy chủ sql: cập nhật các trường trên bảng lớn trong khối nhỏ: cách lấy tình trạng tiến bộ?


Đề xuất của bạn trong # 4 có thể nhanh hơn trong một số trường hợp, nhưng có vẻ như độ phức tạp mã đáng kể để thêm. Tôi muốn bắt đầu đơn giản, và sau đó nếu điều đó không đáp ứng nhu cầu của bạn, hãy xem xét các lựa chọn thay thế.
Bacon Bits

@BaconBits Đồng ý bắt đầu đơn giản. Để công bằng, những đề xuất này không có nghĩa là áp dụng cho tất cả các kịch bản. Câu hỏi là về việc xử lý một bảng rất lớn (100 triệu + hàng).
Solomon Rutzky
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.