Liên quan đến phương pháp luận, tôi tin rằng bạn đang sủa sai cây b;;).
Những gì chúng ta biết:
Trước tiên, hãy củng cố và xem xét những gì chúng ta biết về tình huống:
Những gì chúng ta có thể phỏng đoán:
Tiếp theo, chúng ta có thể cùng nhau xem xét tất cả các điểm dữ liệu này để xem liệu chúng ta có thể tổng hợp các chi tiết bổ sung sẽ giúp chúng ta tìm thấy một hoặc nhiều cổ chai hay không, hoặc hướng tới một giải pháp, hoặc ít nhất là loại trừ một số giải pháp có thể.
Hướng suy nghĩ hiện tại trong các ý kiến là vấn đề chính là truyền dữ liệu giữa SQL Server và Excel. Đó thực sự là trường hợp? Nếu Quy trình được lưu trữ được gọi cho mỗi trong số 800.000 hàng và mất 50 ms cho mỗi cuộc gọi (tức là mỗi hàng), điều đó sẽ tăng thêm tới 40.000 giây (không phải ms). Và nó tương đương với 666 phút (hhmm ;-), hoặc chỉ hơn 11 giờ. Tuy nhiên, toàn bộ quá trình được cho là chỉ mất 7 giờ để chạy. Chúng tôi đã có 4 giờ trong tổng thời gian và chúng tôi thậm chí đã thêm kịp thời để thực hiện các tính toán hoặc lưu kết quả trở lại SQL Server. Vì vậy, một cái gì đó không phải là ở đây.
Nhìn vào định nghĩa của Thủ tục lưu trữ, chỉ có một tham số đầu vào cho @FileID
; không có bộ lọc nào trên @RowID
. Vì vậy, tôi nghi ngờ rằng một trong hai kịch bản sau đây đang xảy ra:
- Quy trình được lưu trữ này không thực sự được gọi cho mỗi hàng, mà thay vào đó mỗi thủ tục
@FileID
, dường như kéo dài khoảng 4000 hàng. Nếu 4000 hàng đã nêu được trả về là một số tiền khá phù hợp, thì chỉ có 200 trong số đó được nhóm trong 800.000 hàng. Và 200 lần thực hiện mất 50 ms mỗi lần chỉ trong 10 giây trong 7 giờ đó.
- Nếu quy trình được lưu trữ này thực sự được gọi cho mỗi hàng, thì lần đầu tiên một giao dịch mới
@FileID
sẽ được thực hiện lâu hơn một chút để kéo các hàng mới vào Vùng đệm, nhưng sau đó, các thực thi 3999 tiếp theo thường sẽ quay lại nhanh hơn do đã được thực hiện lưu trữ, phải không?
Tôi nghĩ rằng việc tập trung vào Quy trình lưu trữ "bộ lọc" này hoặc bất kỳ việc truyền dữ liệu nào từ SQL Server sang Excel, là một cá trích đỏ .
Hiện tại, tôi nghĩ các chỉ số phù hợp nhất về hiệu suất mờ nhạt là:
- Có 800.000 hàng
- Hoạt động trên một hàng tại một thời điểm
- Dữ liệu đang được lưu trở lại SQL Server, do đó "[sử dụng] giá trị từ một số cột để thao tác với các cột khác " [ phas em của tôi là ;-)]
Tôi nghi ngờ rằng:
- trong khi có một số chỗ để cải thiện việc truy xuất dữ liệu và tính toán, làm cho những điều đó tốt hơn sẽ không làm giảm đáng kể thời gian xử lý.
- nút thắt lớn đang phát hành 800.000
UPDATE
báo cáo riêng biệt , đó là 800.000 giao dịch riêng biệt.
Đề xuất của tôi (dựa trên thông tin hiện có):
Khu vực cải tiến lớn nhất của bạn sẽ là cập nhật nhiều hàng cùng một lúc (nghĩa là trong một giao dịch). Bạn nên cập nhật quy trình của bạn để làm việc theo từng FileID
thay vì từng RowID
. Vì thế:
- đọc trong tất cả 4000 hàng cụ thể
FileID
thành một mảng
- mảng nên chứa các phần tử đại diện cho các trường đang được thao tác
- quay vòng qua mảng, xử lý từng hàng như bạn hiện đang làm
- một khi tất cả các hàng trong mảng (nghĩa là cụ thể này
FileID
) đã được tính toán:
- bắt đầu một giao dịch
- gọi mỗi bản cập nhật cho mỗi
RowID
- nếu không có lỗi, cam kết giao dịch
- nếu xảy ra lỗi, khôi phục và xử lý thích hợp
Nếu chỉ mục được nhóm của bạn chưa được xác định như vậy (FileID, RowID)
thì bạn nên xem xét điều đó (như @MikaelEriksson đã đề xuất trong một nhận xét về Câu hỏi). Nó sẽ không giúp những CẬP NHẬT đơn lẻ này, nhưng ít nhất nó sẽ cải thiện một chút các hoạt động tổng hợp, chẳng hạn như những gì bạn đang làm trong quy trình lưu trữ "bộ lọc" đó vì tất cả đều dựa trên FileID
.
Bạn nên xem xét việc chuyển logic sang ngôn ngữ được biên dịch. Tôi sẽ đề nghị tạo một ứng dụng .NET WinForms hoặc thậm chí cả Ứng dụng Console. Tôi thích Ứng dụng Console vì dễ dàng lên lịch thông qua Tác nhân SQL hoặc Tác vụ theo lịch của Windows. Không quan trọng là nó được thực hiện trong VB.NET hay C #. VB.NET có thể phù hợp tự nhiên hơn cho nhà phát triển của bạn, nhưng vẫn sẽ có một số đường cong học tập.
Tôi không thấy bất kỳ lý do tại thời điểm này để chuyển sang SQLCLR. Nếu thuật toán thay đổi thường xuyên, điều đó sẽ gây khó chịu khi phải triển khai lại Hội đồng mọi lúc. Xây dựng lại ứng dụng Console và đặt .exe vào thư mục chia sẻ thích hợp trên mạng để bạn chỉ chạy cùng một chương trình và nó luôn luôn cập nhật, khá dễ thực hiện.
Tôi không nghĩ việc chuyển việc xử lý hoàn toàn sang T-SQL sẽ giúp ích nếu vấn đề là điều tôi nghi ngờ và bạn chỉ đang thực hiện một CẬP NHẬT một lần.
Nếu quá trình xử lý được chuyển sang .NET, thì bạn có thể sử dụng Tham số có giá trị bảng (TVP) để bạn chuyển mảng vào Quy trình được lưu trữ sẽ gọi một THAM GIA UPDATE
đến biến bảng TVP và do đó chỉ là một giao dịch . TVP phải nhanh hơn thực hiện 4000 INSERT
giây được nhóm thành một giao dịch. Nhưng lợi ích đến từ việc sử dụng TVP trên 4000 INSERT
giây trong 1 giao dịch có thể sẽ không đáng kể bằng sự cải thiện được thấy khi chuyển từ 800.000 giao dịch riêng lẻ sang chỉ 200 giao dịch mỗi 4000 hàng.
Tùy chọn TVP không có sẵn cho phía VBA, nhưng ai đó đã nghĩ ra một cách giải quyết có thể đáng để thử nghiệm:
Làm cách nào để cải thiện hiệu suất cơ sở dữ liệu khi chuyển từ VBA sang SQL Server 2008 R2?
NẾU bộ lọc Proc chỉ sử dụng FileID
trong WHERE
mệnh đề và NẾU Proc thực sự được gọi cho mỗi hàng, thì bạn có thể tiết kiệm thời gian xử lý bằng cách lưu trữ kết quả của lần chạy đầu tiên và sử dụng chúng cho phần còn lại của hàng đó FileID
, đúng?
Khi bạn đã xử lý xong mỗi FileID , thì chúng ta có thể bắt đầu nói về xử lý song song. Nhưng điều đó có thể không cần thiết ở thời điểm đó :). Cho rằng bạn đang xử lý 3 phần không chính lý tưởng: giao dịch Excel, VBA và 800k, bất kỳ cuộc thảo luận nào về SSIS, hoặc hình bình hành, hoặc ai biết, là tối ưu hóa sớm / công cụ loại giỏ hàng trước . Nếu chúng tôi có thể giảm quá trình 7 giờ này xuống còn 10 phút hoặc ít hơn, bạn vẫn sẽ nghĩ đến những cách bổ sung để làm cho nó nhanh hơn chứ? Có một thời gian hoàn thành mục tiêu mà bạn có trong tâm trí? Hãy nhớ rằng một khi quá trình xử lý được thực hiện trên mỗi FileID về cơ bản, nếu bạn đã có Ứng dụng Bảng điều khiển VB.NET (ví dụ: dòng lệnh .EXE), sẽ không có gì ngăn bạn chạy một vài FileID đó một lúc :), cho dù thông qua bước CmdExec của Tác nhân SQL hoặc Nhiệm vụ theo lịch trình của Windows, Vân vân.
VÀ, bạn luôn có thể thực hiện một cách tiếp cận "theo giai đoạn" và thực hiện một vài cải tiến tại một thời điểm. Chẳng hạn như bắt đầu với việc thực hiện các cập nhật cho mỗi FileID
và do đó sử dụng một giao dịch cho nhóm đó. Sau đó, xem bạn có thể làm cho TVP hoạt động không. Sau đó, hãy xem về việc lấy mã đó và chuyển nó sang VB.NET (và TVP hoạt động trong .NET để nó sẽ chuyển tốt).
Những gì chúng ta không biết vẫn có thể giúp:
- "Bộ lọc" Quy trình được lưu trữ chạy trên RowID hoặc mỗi FileID ? Chúng ta thậm chí có định nghĩa đầy đủ về Thủ tục lưu trữ đó không?
- Lược đồ đầy đủ của bảng. Cái bàn này rộng bao nhiêu? Có bao nhiêu trường có chiều dài thay đổi? Có bao nhiêu lĩnh vực là NULLable? Nếu có NULLable thì có bao nhiêu NULL?
- Các chỉ mục cho bảng này. Có phân vùng không? Là nén ROW hoặc PAGE đang được sử dụng?
- Bảng này lớn đến mức nào về MB / GB?
- Làm thế nào là bảo trì chỉ mục được xử lý cho bảng này? Làm thế nào phân mảnh được các chỉ số? Làm thế nào cập nhật cho đến nay là số liệu thống kê?
- Có bất kỳ quy trình nào khác ghi vào bảng này trong khi quá trình 7 giờ này đang diễn ra không? Nguồn có thể tranh chấp.
- Có bất kỳ quá trình khác đọc từ bảng này trong khi quá trình 7 giờ này đang diễn ra? Nguồn có thể tranh chấp.
CẬP NHẬT 1:
** Dường như có một số nhầm lẫn về những gì VBA (Visual Basic cho Ứng dụng) và những gì có thể được thực hiện với nó, vì vậy điều này chỉ để đảm bảo tất cả chúng ta đều trên cùng một trang web:
CẬP NHẬT 2:
Thêm một điểm để xem xét: Làm thế nào các kết nối được xử lý? Là mã VBA mở và đóng Kết nối cho mỗi hoạt động, hay nó mở kết nối khi bắt đầu quá trình và đóng nó ở cuối quá trình (tức là 7 giờ sau)? Ngay cả với nhóm kết nối (theo mặc định, nên được bật cho ADO), vẫn sẽ có một tác động khá lớn giữa mở và đóng một lần thay vì mở và đóng 800.200 hoặc 1.600.000 lần. Các giá trị này dựa trên ít nhất 800.000 CẬP NHẬT cộng với 200 hoặc 800 nghìn EXEC (tùy thuộc vào tần suất bộ lọc được lưu trữ thực sự được thực thi).
Vấn đề có quá nhiều kết nối này sẽ tự động được giảm thiểu theo khuyến nghị tôi đã nêu ở trên. Bằng cách tạo một giao dịch và thực hiện tất cả các CẬP NHẬT trong giao dịch đó, bạn sẽ giữ kết nối đó mở và sử dụng lại cho mỗi giao dịch UPDATE
. Việc kết nối có được duy trì mở từ cuộc gọi ban đầu để nhận 4000 hàng trên mỗi lần chỉ định FileID
hay đã đóng sau thao tác "get" đó và mở lại cho các CẬP NHẬT, ít ảnh hưởng hơn vì chúng ta hiện đang nói về sự khác biệt của một trong hai 200 hoặc 400 tổng số kết nối trên toàn bộ quá trình.
CẬP NHẬT 3:
Tôi đã làm một số thử nghiệm nhanh chóng. Xin lưu ý rằng đây là một thử nghiệm quy mô khá nhỏ và không phải là hoạt động chính xác giống nhau (thuần túy INSERT so với EXEC + CẬP NHẬT). Tuy nhiên, sự khác biệt về thời gian liên quan đến cách xử lý các kết nối và giao dịch vẫn có liên quan, do đó thông tin có thể được ngoại suy để có tác động tương đối giống nhau ở đây.
Thông số kiểm tra:
- Phiên bản dành cho nhà phát triển SQL Server 2012 (64-bit), SP2
Bàn:
CREATE TABLE dbo.ManyInserts
(
RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
SomeValue BIGINT NULL
);
Hoạt động:
INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
- Tổng số chèn cho mỗi bài kiểm tra: 10.000
- Đặt lại cho mỗi thử nghiệm:
TRUNCATE TABLE dbo.ManyInserts;
(với bản chất của thử nghiệm này, thực hiện FREEPROCCACHE, FREESYSTEMCACHE và DROPCLEANBUFFERS dường như không thêm nhiều giá trị.)
- Mô hình khôi phục: SIMPLE (và có thể 1 GB miễn phí trong tệp Nhật ký)
- Các thử nghiệm sử dụng Giao dịch chỉ sử dụng một Kết nối duy nhất bất kể có bao nhiêu Giao dịch.
Các kết quả:
Test Milliseconds
------- ------------
10k INSERTs across 10k Connections 3968 - 4163
10k INSERTs across 1 Connection 3466 - 3654
10k INSERTs across 1 Transaction 1074 - 1086
10k INSERTs across 10 Transactions 1095 - 1169
Như bạn có thể thấy, ngay cả khi kết nối ADO với DB đã được chia sẻ trên tất cả các hoạt động, việc nhóm chúng thành các đợt bằng một giao dịch rõ ràng (đối tượng ADO sẽ có thể xử lý việc này) được đảm bảo đáng kể (tức là cải thiện hơn 2 lần) giảm thời gian xử lý tổng thể.