Hiệu suất chậm chèn vài dòng vào bảng lớn


9

Chúng tôi có một quy trình lấy dữ liệu từ các cửa hàng và cập nhật bảng kiểm kê toàn công ty. Bảng này có các hàng cho mọi cửa hàng theo ngày và theo mặt hàng. Tại các khách hàng có nhiều cửa hàng, bảng này có thể nhận được rất lớn - với đơn hàng 500 triệu hàng.

Quá trình cập nhật hàng tồn kho này thường được chạy nhiều lần trong ngày khi các cửa hàng nhập dữ liệu. Những chạy này cập nhật dữ liệu từ chỉ một vài cửa hàng. Tuy nhiên, khách hàng cũng có thể chạy nó để cập nhật, tất cả các cửa hàng trong 30 ngày qua. Trong trường hợp này, quy trình sẽ tạo ra 10 luồng và cập nhật hàng tồn kho của mỗi cửa hàng trong một luồng riêng biệt.

Khách hàng đang phàn nàn rằng quá trình này mất nhiều thời gian. Tôi đã mô tả quá trình và thấy rằng một truy vấn mà CHỈ vào bảng này sẽ tiêu tốn nhiều thời gian hơn tôi mong đợi. CHERTN này đôi khi hoàn thành trong 30 giây.

Khi tôi chạy lệnh INSERT SQL ad-hoc đối với bảng này (giới hạn bởi BEGIN TRAN và ROLLBACK), SQL ad-hoc hoàn thành theo thứ tự mili giây.

Các truy vấn thực hiện chậm là dưới đây. Ý tưởng là để ghi lại các bản ghi không có ở đó và sau đó để CẬP NHẬT chúng khi chúng tôi tính toán các bit dữ liệu khác nhau. Bước trước trong quy trình đã xác định các mục cần được cập nhật, thực hiện một số tính toán và nhồi kết quả vào bảng tempdb Update_Item_Work. Quá trình này đang chạy trong 10 luồng riêng biệt và mỗi luồng có GUID riêng trong Update_Item_Work.

INSERT INTO Inventory
(
    Inv_Site_Key,
    Inv_Item_Key,
    Inv_Date,
    Inv_BusEnt_ID,
    Inv_End_WtAvg_Cost
)
SELECT DISTINCT
    UpdItemWrk_Site_Key,
    UpdItemWrk_Item_Key,
    UpdItemWrk_Date,
    UpdItemWrk_BusEnt_ID,
    (CASE UpdItemWrk_Set_WtAvg_Cost WHEN 1 THEN UpdItemWrk_WtAvg_Cost ELSE 0 END)
FROM tempdb..Update_Item_Work (NOLOCK)
WHERE UpdItemWrk_GUID = @GUID
AND NOT EXISTS
    -- Only insert for site/item/date combinations that don't exist
    (SELECT *
    FROM Inventory (NOLOCK)
    WHERE Inv_Site_Key = UpdItemWrk_Site_Key
    AND Inv_Item_Key = UpdItemWrk_Item_Key
    AND Inv_Date = UpdItemWrk_Date)

Bảng Inventory có 42 cột, hầu hết theo dõi số lượng và số lượng để điều chỉnh hàng tồn kho khác nhau. sys.dm_db_index_physical_stats nói rằng mỗi hàng có khoảng 242 byte, vì vậy tôi hy vọng khoảng 33 hàng sẽ phù hợp trên một trang 8 KB.

Bảng được nhóm trên ràng buộc duy nhất (Inv_Site_Key, Inv_Item_Key, Inv_Date). Tất cả các khóa là DECIMAL (15,0) và ngày là SMALLDATETIME. Có một khóa chính IDENTITY (không bao gồm) và 4 chỉ mục khác. Tất cả các chỉ mục và ràng buộc phân cụm được xác định bằng một tường minh (FILLFACTOR = 90, PAD_INDEX = ON).

Tôi đã xem trong tệp nhật ký để đếm số lần chia trang. Tôi đã đo khoảng 1.027 lần phân tách trên chỉ mục được nhóm và 1.724 lần phân tách trên một chỉ mục khác, nhưng tôi đã không ghi lại khoảng thời gian xảy ra. Một tiếng rưỡi sau, tôi đo được 7.035 lần chia trang trên chỉ mục được nhóm.

Kế hoạch truy vấn tôi đã chụp trong trình lược tả trông như thế này:

Rows         Executes     StmtText                                                                                                                                             
----         --------     --------                                                                                                                                             
490          1            Sequence                                                                                                                                             
0            1              |--Index Update
0            1              |    |--Collapse
0            1              |         |--Sort
0            1              |              |--Filter
996          1              |                   |--Table Spool                                                                                                                 
996          1              |                        |--Split                                                                                                                  
498          1              |                             |--Assert
0            0              |                                  |--Compute Scalar
498          1              |                                       |--Clustered Index Update(UK_Inventory)
498          1              |                                            |--Compute Scalar
0            0              |                                                 |--Compute Scalar
0            0              |                                                      |--Compute Scalar
498          1              |                                                           |--Compute Scalar
498          1              |                                                                |--Top
498          1              |                                                                     |--Nested Loops
498          1              |                                                                          |--Stream Aggregate
0            0              |                                                                          |    |--Compute Scalar
498          1              |                                                                          |         |--Clustered Index Seek(tempdb..Update_Item_Work)
498          498            |                                                                          |--Clustered Index Seek(Inventory)
0            1              |--Index Update(UX_Inv_Exceptions_Date_Site_Item)
0            1              |    |--Collapse
0            1              |         |--Sort
0            1              |              |--Filter
996          1              |                   |--Table Spool
490          1              |--Index Update(UX_Inv_Date_Site_Item)
490          1                   |--Collapse
980          1                        |--Sort
980          1                             |--Filter
996          1                                  |--Table Spool                                                                                       

Nhìn vào các truy vấn so với các dmv khác nhau, tôi thấy truy vấn đang chờ trên PAGEIOLATCH_EX với thời lượng 0 trên một trang trong bảng Khoảng không quảng cáo này. Tôi không thấy bất kỳ sự chờ đợi hay chặn trên ổ khóa.

Máy này có bộ nhớ khoảng 32 GB. Nó đang chạy SQL Server 2005 Standard Edition, mặc dù họ sẽ sớm nâng cấp lên 2008 R2 Enterprise Edition. Tôi không có số liệu về mức độ lớn của bảng kiểm kê về mức độ sử dụng đĩa, nhưng tôi có thể lấy số đó nếu cần. Đây là một trong những bảng lớn nhất trong hệ thống này.

Tôi đã chạy một truy vấn đối với sys.dm_io_virtual_file_stats và thấy ghi trung bình chờ đợi với tempdb lên tới 1,1 giây . Cơ sở dữ liệu trong đó bảng này được lưu trữ có thời gian chờ ghi trung bình là ~ 350 ms. Nhưng họ chỉ khởi động lại máy chủ của họ sau mỗi 6 tháng hoặc lâu hơn, vì vậy tôi không biết thông tin này có liên quan hay không. tempdb được trải rộng trên 4 tệp khác nhau Họ có 3 tệp khác nhau cho cơ sở dữ liệu chứa bảng Inventory.

Tại sao truy vấn này sẽ mất nhiều thời gian để CHỈ một vài hàng khi chạy với nhiều luồng khác nhau khi một INSERT duy nhất rất nhanh?

- CẬP NHẬT -

Dưới đây là số độ trễ trên mỗi ổ đĩa bao gồm cả byte đọc. Như bạn có thể thấy, hiệu suất tempdb là nghi vấn. Bảng Inventory có trong PDICompany_252_01.mdf, PDICompany_252_01_Second.ndf hoặc PDICompany_252_01_Third.ndf.

ReadLatencyWriteLatencyLatencyAvgBPerRead AvgBPerWriteAvgBPerTransferDriveDB                     physical_name
         42        1112    623       62171       67654          65147R:   tempdb                 R:\Microsoft SQL Server\Tempdb\tempdev1.mdf
         38        1101    615       62122       67626          65109S:   tempdb                 S:\Microsoft SQL Server\Tempdb\tempdev2.ndf
         38        1101    615       62136       67639          65123T:   tempdb                 T:\Microsoft SQL Server\Tempdb\tempdev3.ndf
         38        1101    615       62140       67629          65119U:   tempdb                 U:\Microsoft SQL Server\Tempdb\tempdev4.ndf
         25         341     71       92767       53288          87009X:   PDICompany             X:\Program Files\PDI\Enterprise\Databases\PDICompany_Third.ndf
         26         339     71       90902       52507          85345X:   PDICompany             X:\Program Files\PDI\Enterprise\Databases\PDICompany_Second.ndf
         10         231     90       98544       60191          84618W:   PDICompany_FRx         W:\Program Files\PDI\Enterprise\Databases\PDICompany_FRx.mdf
         61         137     68        9120        9181           9125W:   model                  W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\modeldev.mdf
         36         113     97        9376        5663           6419V:   model                  V:\Microsoft SQL Server\Logs\modellog.ldf
         22          99     34       92233       52112          86304W:   PDICompany             W:\Program Files\PDI\Enterprise\Databases\PDICompany.mdf
          9          20     10       25188        9120          23538W:   master                 W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\master.mdf
         20          18     19       53419       10759          40850W:   msdb                   W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\MSDBData.mdf
         23          18     19      947956       58304         110123V:   PDICompany_FRx         V:\Program Files\PDI\Enterprise\Databases\PDICompany_FRx_1.ldf
         20          17     17      828123       55295         104730V:   PDICompany             V:\Program Files\PDI\Enterprise\Databases\PDICompany.ldf
          5          13     13       12308        4868           5129V:   master                 V:\Microsoft SQL Server\Logs\mastlog.ldf
         11          13     13       22233        7598           8513V:   PDIMaster              V:\Program Files\PDI\Enterprise\Databases\PDIMaster.ldf
         14          11     13       13846        9540          12598W:   PDIMaster              W:\Program Files\PDI\Enterprise\Databases\PDIMaster.mdf
         13          11     11       22350        1107           1110V:   msdb                   V:\Microsoft SQL Server\Logs\MSDBLog.ldf
         17           9      9      745437       11821          23249V:   PDIFoundation          V:\Program Files\PDI\Enterprise\Databases\PDIFoundation.ldf
         34           8     31       29490       33725          30031W:   PDIFoundation          W:\Program Files\PDI\Enterprise\Databases\PDIFoundation.mdf
          5           8      8       61560       61236          61237V:   tempdb                 V:\Microsoft SQL Server\Logs\templog.ldf
         13           6     11        8370       35087          16785W:   SAHost_Company01       W:\Program Files\PDI\Enterprise\Databases\SAHostCompany.mdf
          2           6      5       56235       33667          38911W:   SAHost_Company01       W:\Program Files\PDI\Enterprise\Databases\SAHost_Company_01_log.LDF

Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Paul White 9

Câu trả lời:


4

Có vẻ như việc phân chia trang chỉ mục được phân cụm của bạn sẽ rất khó khăn vì chỉ mục được phân cụm chứa dữ liệu thực tế và điều này sẽ cần các trang mới được phân bổ và dữ liệu được chuyển sang các trang này. Điều này có khả năng gây ra khóa trang và do đó chặn.

Cũng cần nhớ rằng khóa chỉ mục được nhóm của bạn là 21 byte và điều này sẽ cần được lưu trữ trong tất cả các chỉ mục phụ của bạn dưới dạng dấu trang.

Bạn đã xem xét làm cho cột nhận dạng khóa chính của bạn thành chỉ mục được nhóm của bạn, điều này không chỉ làm giảm kích thước của các chỉ mục khác của bạn, điều đó cũng có nghĩa là bạn sẽ giảm số lượng phân chia trang trong chỉ mục được nhóm của bạn. Rất đáng để thử nếu bạn có thể xây dựng lại chỉ số của mình.


1

Với cách tiếp cận đa luồng, tôi cảnh giác với việc chèn vào bảng mà trước tiên bạn phải kiểm tra sự tồn tại trước của khóa. Điều đó nói với tôi rằng có một vấn đề tương tranh trên chỉ mục PK đối với bảng đó cho dù có bao nhiêu luồng. Vì lý do tương tự, tôi không thích gợi ý NOLOCK trên bảng kiểm kê vì có vẻ như sẽ xảy ra lỗi nếu các luồng khác nhau có thể viết cùng một khóa (lược đồ phân vùng có loại bỏ khả năng đó không?). Tôi tò mò về việc tăng tốc độ lớn như thế nào trên phần giới thiệu ban đầu của nhiều luồng vì nó phải hoạt động tốt ở một số điểm.

Một cái gì đó để thử là làm cho truy vấn đọc giống như một hoạt động hàng loạt và chuyển đổi "nơi không tồn tại" thành "chống tham gia". (cuối cùng trình tối ưu hóa có thể chọn bỏ qua nỗ lực này). Như đã đề cập ở trên, tôi sẽ xóa gợi ý NOLOCK trên bảng đích trừ khi có thể phân vùng đã đảm bảo không có xung đột chính giữa các luồng.

 INSERT INTO i (...)
 SELECT DISTINCT ...             
   FROM tempdb..Update_Item_Work t (NOLOCK) -- nolock okay on read table
   left join Inventory i -- use without NOLOCK because PK is written inter-thread
     on i.Inv_Site_Key = t.UpdItemWrk_Site_Key
    and i.Inv_Item_Key = t.UpdItemWrk_Item_Key
    and i.Inv_Date = t.UpdItemWrk_Date
  where i.Inv_Site_Key is null   -- where not exist in inventory
    and UpdItemWrk_GUID = @GUID  -- for this thread

Thời gian chạy như một cơ sở, bạn có thể chạy lại với gợi ý hợp nhất ("tham gia trái" -> "tham gia hợp nhất trái") như một khả năng khác. Bạn có thể nên có một chỉ mục trên bảng tạm thời (UpdItemWrk_Site_Key, UpdItemWrk_Item_Key, UpdItemWrk_Date) cho gợi ý hợp nhất.

Tôi không biết liệu các phiên bản mới hơn của SQL Server 2008/2012 có thể tự động song song các kết hợp lớn hơn của biểu mẫu này hay không cho phép bạn xóa phân vùng dựa trên GUID.

Để khuyến khích việc tham gia chỉ xảy ra trên các mục riêng biệt thay vì tất cả các mục, các mệnh đề "chọn riêng biệt ... từ ..." có thể được chuyển đổi thành "chọn * từ (chọn riêng biệt ... từ ...)" trước tiếp tục với sự tham gia Điều này chỉ có thể tạo ra sự khác biệt đáng chú ý nếu khác biệt là lọc nhiều hàng. Một lần nữa trình tối ưu hóa có thể bỏ qua nỗ lực này.

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.