Đối với SQL Server, làm thế nào để sửa lỗi cập nhật bảng song song đồng thời?


7

Tôi phải cập nhật tất cả các bản ghi (thêm Hướng dẫn) trên hai cột trống (được lập chỉ mục) gồm 150 bảng, mỗi bảng có khoảng 50 nghìn bản ghi (sử dụng tập lệnh để tạo 40k cập nhật cùng một lúc trong c # và đăng lên máy chủ) và chính xác là 4 bản hiện có cột.

Trên máy cục bộ của tôi (RAM 16 GB, Samsung 850 500, SQL Server 2014, lõi i5) khi tôi cố gắng chạy song song 10 bảng, mất tổng cộng 13 phút , trong khi nếu tôi chạy 5 thì quá trình hoàn tất chỉ trong 1,7 phút .

Tôi hiểu rằng có một cái gì đó bận rộn ở cấp độ đĩa, nhưng tôi cần một số trợ giúp về cách định lượng sự khác biệt lớn này trong thời gian.

Có một khung nhìn SQL Server DB chính xác mà tôi có thể kiểm tra sự khác biệt này không? Có cách nào chính xác để tìm ra một phần cứng nhất định có bao nhiêu bản cập nhật bảng tôi có thể chạy song song không ?? (máy chủ thử nghiệm thực sự có nhiều RAM và 10k vòng / phút).

Bất cứ ai cũng có thể chỉ ra một cái gì đó mà tôi có thể cải thiện trên Máy chủ SQL để cải thiện thời gian cho 10 bảng đang chạy song song?

Tôi đã thử tăng kích thước Tăng trưởng tự động lên 100MB từ 10 MB để cải thiện thời lượng Hàng đợi đĩa (từ khoảng 5 đến 0,1) nhưng thực tế nó không làm giảm tổng thời gian nhiều như vậy.

Tôi đã hỏi chính xác câu hỏi tương tự trên stackoverflow, nhưng không nhận được bất kỳ câu trả lời hữu ích nào cho đến nay, vì vậy một số hoặc bất kỳ cái nhìn sâu sắc / trợ giúp nào sẽ vô cùng hữu ích. :)


Deb, thu thập thêm số liệu thống kê trên cơ sở dữ liệu trong khi các truy vấn đang chạy để xác định nút cổ chai. Tôi thấy bạn đã đề cập đến chiều dài hàng đợi đĩa, nhưng đó chỉ là một phần nhỏ của một bức tranh lớn hơn nhiều. Tôi sẽ khuyên bạn nên theo dõi việc sử dụng CPU, sử dụng bộ nhớ và các thống kê đĩa khác (ghi / giây, ms trên mỗi lần ghi sẽ là một nơi tốt để bắt đầu).
Chris Bergin

Ở đầu bên kia là một đầu viết. Song song chỉ kết thúc trong một hàng đợi. Đặt cược tốt nhất của bạn là giữ cho một chủ đề bận rộn. Tôi sử dụng BlockingCollection - tạo các lệnh trên nhà sản xuất.
paparazzo

Câu trả lời:


3

Đưa ra mã trong câu trả lời của bạn, rất có thể bạn sẽ cải thiện hiệu suất bằng cách thực hiện hai thay đổi sau:

  • Bắt đầu lô truy vấn với BEGIN TRANvà kết thúc lô bằng COMMIT TRAN:

  • Giảm số lượng cập nhật mỗi đợt xuống dưới 5000 để tránh leo thang khóa (thường xảy ra ở 5000 khóa). Hãy thử 4500.

Thực hiện hai điều đó sẽ làm giảm số lượng lớn các hoạt động ghi nhật ký tran và khóa / mở khóa mà bạn hiện đang tạo bằng cách thực hiện các câu lệnh DML riêng lẻ.

Thí dụ:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"BEGIN TRAN;
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 1;
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 2;
        ...
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 4500;
        COMMIT TRAN;
        ", conn));

CẬP NHẬT

Câu hỏi hơi thưa thớt về các chi tiết. Mã ví dụ chỉ được hiển thị trong một câu trả lời .

Một lĩnh vực gây nhầm lẫn là mô tả đề cập đến việc cập nhật hai cột, tuy nhiên mã ví dụ chỉ hiển thị một cột duy nhất đang được cập nhật. Câu trả lời của tôi ở trên được dựa trên mã, do đó nó chỉ hiển thị một cột duy nhất. Nếu thực sự có hai cột để cập nhật, thì cả hai cột phải được cập nhật trong cùng một UPDATEcâu lệnh:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"BEGIN TRAN;
        UPDATE [TestTable]
        SET    Column5 = 'some unique value',
               ColumnN = 'some unique value'
        WHERE  ID = 1;
        UPDATE [TestTable]
               SET Column5 = 'some unique value',
               SET ColumnN = 'some unique value'
        WHERE  ID = 2;
        ...
        UPDATE [TestTable]
               SET Column5 = 'some unique value',
               SET ColumnN = 'some unique value'
        WHERE  ID = 4500;
        COMMIT TRAN;
        ", conn));

Một vấn đề khác không rõ ràng là dữ liệu "duy nhất" đến từ đâu? Câu hỏi đề cập rằng các giá trị duy nhất là GUID. Là những thứ này được tạo ra trong lớp ứng dụng? Có phải chúng đến từ một nguồn dữ liệu khác mà lớp ứng dụng biết và cơ sở dữ liệu không? Điều này rất quan trọng bởi vì, tùy thuộc vào câu trả lời cho những câu hỏi này, có thể có ý nghĩa khi hỏi:

  1. Thay vào đó, GUID có thể được tạo trong SQL Server không?
  2. Nếu có với # 1, thì có lý do nào để tạo mã này từ mã ứng dụng thay vì thực hiện một vòng lặp đơn giản trong T-SQL không?

Nếu "có" thành # 1 nhưng vì lý do nào đó, cần phải được tạo trong .NET, thì bạn có thể sử dụng NEWID()và tạo các UPDATEcâu lệnh hoạt động trên phạm vi của các hàng, trong trường hợp đó bạn không cần BEGIN TRAN/ 'CAM KẾT mỗi câu lệnh có thể xử lý tất cả 4500 hàng trong một lần chụp:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"UPDATE [TestTable]
        SET    Column5 = NEWID(),
               ColumnN = NEWID()
        WHERE  ID BETWEEN 1 and 4500;
        ", conn));

Nếu "có" với # 1 và không có lý do thực sự nào để các CẬP NHẬT này được tạo trong .NET, thì bạn có thể thực hiện các thao tác sau:

DECLARE @BatchSize INT = 4500, -- this could be an input param for a stored procedure
        @RowsAffected INT = 1, -- needed to enter loop
        @StartingID INT = 1; -- initial value

WHILE (@RowsAffected > 0)
BEGIN
  UPDATE TOP (@BatchSize) tbl
  SET    tbl.Column5 = NEWID(),
         tbl.ColumnN = NEWID()
  FROM   [TestTable] tbl
  WHERE  tbl.ID BETWEEN @StartingID AND (@StartingID + @BatchSize - 1);

  SET @RowsAffected = @@ROWCOUNT;
  SET @StartingID += @BatchSize;
END;

Mã ở trên chỉ hoạt động nếu các IDgiá trị không thưa thớt hoặc ít nhất là nếu các giá trị không có khoảng trống lớn hơn @BatchSize, sao cho có ít nhất 1 hàng được cập nhật trong mỗi lần lặp. Mã này cũng giả định rằng IDtrường là Chỉ mục cụm. Các giả định này có vẻ hợp lý với mã ví dụ được cung cấp.

Tuy nhiên, nếu các IDgiá trị có khoảng trống lớn hoặc nếu IDtrường không phải là Chỉ mục cụm, thì bạn chỉ có thể kiểm tra các hàng chưa có giá trị:

DECLARE @BatchSize INT = 4500, -- this could be an input param for a stored procedure
        @RowsAffected INT = 1; -- needed to enter loop

WHILE (@RowsAffected > 0)
BEGIN
  UPDATE TOP (@BatchSize) tbl
  SET    tbl.Column5 = NEWID(),
         tbl.ColumnN = NEWID()
  FROM   [TestTable] tbl
  WHERE  tbl.Col1 IS NULL;

  SET @RowsAffected = @@ROWCOUNT;
END;

NHƯNG , nếu "không" đến # 1 và các giá trị đến từ .NET vì một lý do chính đáng, chẳng hạn như các giá trị duy nhất cho mỗi giá trị IDđã tồn tại trong một nguồn khác, thì bạn vẫn có thể tăng tốc độ này (ngoài đề xuất ban đầu của tôi) bằng cách cung cấp một bảng dẫn xuất:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"BEGIN TRAN;

        UPDATE tbl
        SET    tbl.Column5 = tmp.Col1,
               tbl.ColumnN = tmp.Col2
        FROM   [TestTable] tbl
        INNER JOIN (VALUES
          (1, 'some unique value A', 'some unique value B'),
          (2, 'some unique value C', 'some unique value D'),
          ...
          (1000, 'some unique value N1', 'some unique value N2')
                   ) tmp (ID, Col1, Col2)
                ON tmp.ID = tbl.ID;

        UPDATE tbl
        SET    tbl.Column5 = tmp.Col1,
               tbl.ColumnN = tmp.Col2
        FROM   [TestTable] tbl
        INNER JOIN (VALUES
          (1001, 'some unique value A2', 'some unique value B2'),
          (1002, 'some unique value C2', 'some unique value D2'),
          ...
          (2000, 'some unique value N3', 'some unique value N4')
                   ) tmp (ID, Col1, Col2)
                ON tmp.ID = tbl.ID;

        COMMIT TRAN;
        ", conn));

Tôi tin rằng giới hạn về số lượng hàng có thể được tham gia VALUESlà 1000, vì vậy tôi đã nhóm hai bộ lại với nhau trong một giao dịch rõ ràng. Bạn có thể kiểm tra với tối đa 4 bộ trong số này UPDATEđể thực hiện 4000 mỗi giao dịch và giữ dưới giới hạn 5000 khóa.


3

Dựa trên câu trả lời của riêng bạn , có vẻ như:

  1. Bạn đang cập nhật cột trống thứ nhất và thứ hai trong các báo cáo cập nhật riêng

  2. Các cột trống là kiểu dữ liệu varchar

Tôi chưa có đủ đại diện trên DBA để nhận xét (ban đầu tôi đã thấy phiên bản này bạn đăng chéo trên Stack Overflow), vì vậy sẽ trả lời về giả định đó.

Nếu vậy, bạn có thể mắc một lỗi phổ biến đối với những người đến với SQL từ các ngôn ngữ thủ tục: suy nghĩ về các bảng SQL theo thủ tục, cập nhật từng hàng và từng cột một lần.

SQL muốn bạn thực hiện các hoạt động dựa trên thiết lập , trong đó bạn nói với SQL những gì bạn muốn làm cho tất cả các hàng trong một truy vấn / câu lệnh. Sau đó, công cụ truy vấn SQL Server có thể tìm ra cách tốt nhất để thực sự thay đổi đó xảy ra với tất cả các hàng. Bằng cách thực hiện cập nhật liên tục, bạn sẽ ngăn SQL Server thực hiện công việc tốt nhất.

Có thể bạn nhận thức rõ về điều này và bản chất của các giá trị bạn phải cập nhật làm cho các cập nhật theo từng hàng trở nên thiết yếu, nhưng ngay cả khi đó tôi nghĩ rằng bạn có thể cập nhật cả hai cột cho một hàng trong một lần, giảm một nửa tổng số cập nhật bạn phải làm.

Nếu bạn có mức độ linh hoạt về các giá trị duy nhất trong các cột của mình, có lẽ bạn có thể cập nhật toàn bộ một bảng với một truy vấn SQL duy nhất. Đối với các giá trị GUID thực, một truy vấn dọc theo dòng:

update TestTable
set    Column5 = NEWID()
       ,Column6 = NEWID()

sẽ cung cấp cho bạn uniqueidentifiercác giá trị duy nhất trong mỗi ô. NEWID được ghi lại ở đây nếu bạn chưa từng thấy nó trước đây. Sau đó, bạn chỉ cần lặp lại truy vấn này 150 lần cho các bảng riêng biệt, có thể được song song dễ dàng; Tôi cá là nó cũng nhanh hơn.

Hoặc, nếu bạn cần một cái gì đó dựa trên số, bạn có thể áp dụng các số duy nhất như thế này:

with cteNumbers as (
    select  Column5
            ,Column6
            ,Row_Number() over (order by id) as RowNo
    from TestTable
    )
update cteNumbers
set Column5=RowNo
    ,Column6=RowNo

Mặc dù tôi nghi ngờ đó không phải là những gì bạn đang cố gắng làm; và trong mọi trường hợp, nếu idcột của bạn là tự động tăng int, bạn chỉ có thể sử dụng trực tiếp thay vì tạo một cột Row_Number()trên nó.

Nếu bạn muốn một cái gì đó dựa trên số tăng nhưng không chỉ bao gồm số đó, bạn có thể xây dựng một biểu thức xung quanh RowNođể đạt được những gì bạn muốn.

Bất cứ nơi nào có thể, sử dụng các hoạt động dựa trên tập hợp là một điều cần thiết tuyệt đối cho hiệu suất SQL hiệu quả.


cảm ơn câu trả lời của bạn, thực sự tôi đã xem xét cách tiếp cận dựa trên SET nhưng không thể sử dụng nó vì dữ liệu cho các cột đó đến từ một nơi khác. Về cơ bản, tôi chỉ cập nhật GUID mới vào ID của hồ sơ cụ thể.
Nợ

2

Tìm thấy giải pháp. :)

Thay vì chạy các truy vấn cập nhật 40K cùng một lúc (Tôi tạo tập lệnh cập nhật 40k câu lệnh cập nhật như đã nêu trong nhận xét ở trên) nếu tôi giảm số đó xuống còn một nửa - truy vấn cập nhật 20k cùng một lúc có một cải tiến rất lớn - 10 bảng song song phải mất tổng cộng 1,3 phút bây giờ - bây giờ tôi có thể tiếp tục.

Đây là mã cập nhật: nhập mô tả hình ảnh ở đây

Bây giờ mã đã được thay đổi để làm 20k cùng một lúc.

Vì vậy, về cơ bản trước đây, nó đã chạy 10 (chủ đề) X 40k truy vấn cập nhật = 400k truy vấn cập nhật đồng thời ở lần chạy đầu tiên và sau đó là 10 (chủ đề) X 10k truy vấn cập nhật, để cập nhật tất cả các bản ghi 50k trong 10 loại khác nhau.

Và, bây giờ nó:

  1. 10 (chủ đề) X 20k truy vấn cập nhật = 200k truy vấn cập nhật đồng thời
  2. 10 (chủ đề) X 20k truy vấn cập nhật = 200k truy vấn cập nhật đồng thời
  3. 10 (chủ đề) X 10k truy vấn cập nhật = 100k truy vấn cập nhật

Kết quả: Trước: 13 phút , Sau: 1,8 phút

Bây giờ tôi đang kiểm tra để tìm ra sự kết hợp tốt nhất (nhanh nhất!) Để cập nhật 150 bảng đó bằng nhiều luồng cùng một lúc. Có lẽ tôi có thể cập nhật số lượng bảng cao hơn song song với bản cập nhật đồng thời thấp hơn như 5k (từ 20k) nhưng tôi sẽ bận kiểm tra ngay bây giờ.


Không chắc chắn nếu tôi có thể phát triển điều này thành một câu trả lời đầy đủ nhưng chỉ là một ý tưởng, bạn đã xem xét sử dụng một tham số có giá trị bảng cho điều này chưa? Mỗi luồng sẽ chỉ tải một phần dữ liệu vào tham số này và tập lệnh sẽ có dạng như thế này : UPDATE t SET column5 = tvp.Value FROM [TestTable] AS t INNER JOIN @YourTableValuedParameter AS tvp ON t.id = tvp.id;. (Tôi nghĩ rằng đề xuất của @ srutzky về việc giữ số lượng cập nhật cho mỗi luồng ở mức dưới 5k vẫn sẽ được áp dụng.)
Andriy M

Vui lòng cho biết bạn đang thực sự cập nhật cả hai cột trong một tuyên bố
paparazzo

@Frisbee có, cả hai cột cùng một lúc. Đây là một ví dụ trong một kịch bản khác mà tôi phải cập nhật (thực sự đồng bộ hóa từ một bảng khác) nhiều hơn hai cột. Thật vậy, đề nghị của srutzky về việc giữ số lượng cập nhật cho mỗi luồng ở mức dưới 5k vẫn được áp dụng và hiện tôi đã chấp nhận câu trả lời của anh ấy là câu trả lời tốt nhất cho kịch bản của tôi.
Nợ

0

Không có chế độ xem ma thuật nào sẽ cho bạn biết có bao nhiêu luồng hoạt động tốt hơn cho phần cứng nhất định và như với tất cả các câu hỏi hay, câu trả lời là "nó phụ thuộc". Bạn phải xem xét rằng có thể có tải khác xảy ra tại thời điểm đó hoặc truy vấn của bạn có trọng số hơn trong CPU hoặc I / O. Nhưng những gì bạn có thể làm, và âm thanh như bạn đang làm là thử nghiệm. Bạn cũng có thể muốn ném vào một biến khác, MAXDOP.

Nếu có thể, trong C #, chỉ cần cho phép số lượng chủ đề được biến đổi (đọc từ db hoặc từ tệp cấu hình), sau đó bạn có thể điều chỉnh truy vấn của mình một cách nhanh chóng.

Mặc dù có thể không có chế độ xem ma thuật, nhưng bạn có thể có thể tổng hợp thời gian chờ đợi trên các spids trong mỗi lần chạy để xem nơi chờ đợi và giới hạn.


MAXDOP có vẻ như là một tùy chọn cấp độ Máy chủ Sql (cao hơn) và không phải là tùy chọn cấp độ cơ sở dữ liệu, và do đó tôi không thể thay đổi nó trên cơ sở dữ liệu sản xuất. Bạn có chắc chắn rằng việc tăng (bây giờ tôi sử dụng mặc định) sẽ giúp ích trong việc cập nhật ?? Có lẽ bạn có thể chia sẻ một số liên kết hữu ích hỗ trợ lý thuyết của bạn có lẽ .....
Deb

Bây giờ tôi đã thử nghiệm với MAXDOP = 4 (cho 4 CPU vật lý) và không có cải thiện. Hoàn nguyên về mặc định.
Nợ

Tôi đã không nói rằng nó chắc chắn sẽ giúp ích, nhưng đó chỉ là một biến số khác để cho phép cấu hình tốt hơn. Nếu bạn có 4 lõi và bạn đặt MAXDOP = 4 thì nó cũng giống như để nó một mình. Hãy để nó sang một bên và tập trung vào phần khác của bình luận. Cấu hình số lượng chủ đề và kiểm tra cho từng môi trường. 4 chủ đề có thể là tốt nhất trong thử nghiệm và 20 có thể là tốt nhất trong sản xuất. Chúng tôi sử dụng gói SSIS cho phép đầu vào biến đổi và chúng tôi có thể đặt số lượng luồng.
paulbarbin

Điều này cho phép chúng ta hai điều, khả năng điều chỉnh trong các môi trường khác nhau nhưng cũng có khả năng điều chỉnh trong sản xuất khi những điều khác nhau đang diễn ra. Khi chúng tôi lần đầu tiên bắt đầu, quá trình ban đầu đã mất 24 giờ. Chúng tôi đã "song song hóa" nó và có quy trình hàng tháng xuống còn 4 giờ mà không thay đổi quy trình được lưu trữ TẠI TẤT CẢ. Sau đó, khi quá trình sản xuất trở nên bận rộn hơn với các quy trình khác, chúng tôi đã phải cắt giảm số lượng luồng (và MAXDOP) để cho phép các quy trình khác có thời gian kết thúc.
paulbarbin
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.