Dữ liệu và hiệu suất khổng lồ trong SQL Server


20

Tôi đã viết một ứng dụng với phụ trợ SQL Server thu thập và lưu trữ và số lượng hồ sơ cực lớn. Tôi đã tính toán rằng, vào lúc cao điểm, số lượng hồ sơ trung bình nằm ở đâu đó trong đại lộ 3-4 tỷ mỗi ngày (20 giờ hoạt động).

Giải pháp ban đầu của tôi (trước khi tôi thực hiện tính toán dữ liệu thực tế) là ứng dụng của tôi chèn các bản ghi vào cùng một bảng được khách hàng của tôi truy vấn. Điều đó đã sụp đổ và bị đốt cháy khá nhanh, rõ ràng, bởi vì không thể truy vấn một bảng có nhiều bản ghi được chèn vào.

Giải pháp thứ hai của tôi là sử dụng 2 cơ sở dữ liệu, một cho dữ liệu mà ứng dụng nhận được và một cho dữ liệu sẵn sàng cho khách hàng.

Ứng dụng của tôi sẽ nhận được dữ liệu, chia thành các lô ~ 100 nghìn bản ghi và chèn số lượng lớn vào bảng phân tầng. Sau khi ~ 100k ghi lại, ứng dụng sẽ nhanh chóng tạo một bảng phân tầng khác có cùng lược đồ như trước và bắt đầu chèn vào bảng đó. Nó sẽ tạo một bản ghi trong bảng công việc với tên của bảng có 100k bản ghi và một quy trình được lưu trữ ở phía Máy chủ SQL sẽ chuyển dữ liệu từ (các) bảng phân tầng sang bảng sản xuất sẵn sàng của khách hàng, sau đó thả bảng bảng tạm thời được tạo bởi ứng dụng của tôi.

Cả hai cơ sở dữ liệu có cùng một bộ 5 bảng với cùng một lược đồ, ngoại trừ cơ sở dữ liệu dàn có bảng công việc. Cơ sở dữ liệu dàn không có ràng buộc toàn vẹn, khóa, chỉ mục, v.v ... trên bảng nơi phần lớn các bản ghi sẽ nằm. Dưới đây, tên bảng là SignalValues_staging. Mục tiêu là để ứng dụng của tôi chuyển dữ liệu vào SQL Server càng nhanh càng tốt. Quy trình tạo bảng khi đang di chuyển để chúng có thể dễ dàng được di chuyển hoạt động khá tốt.

Sau đây là 5 bảng có liên quan từ cơ sở dữ liệu dàn của tôi, cộng với bảng công việc của tôi:

Bàn dàn Quy trình được lưu trữ mà tôi đã viết xử lý việc di chuyển dữ liệu từ tất cả các bảng phân tầng và đưa nó vào sản xuất. Dưới đây là một phần của quy trình được lưu trữ của tôi chèn vào sản xuất từ ​​các bảng phân tầng:

-- Signalvalues jobs table.
SELECT *
      ,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM 
(
    SELECT JobId 
           ,ProcessingComplete  
           ,SignalValueStagingTableName AS 'TableName'
           ,(DATEDIFF(SECOND, (SELECT last_user_update
                              FROM sys.dm_db_index_usage_stats
                              WHERE database_id = DB_ID(DB_NAME())
                                AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
                     ,GETUTCDATE())) SecondsSinceLastUpdate
    FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
   OR cte.SecondsSinceLastUpdate >= 120

DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)

DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128) 
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)

WHILE @i > 0
BEGIN

    SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
    FROM #JobsToProcess 
    WHERE RowIndex = @i 

    SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'

    SET @sqlStatement = N'

        --Signal values staging table.
        SELECT svs.* INTO #sValues
        FROM '+ @qualifiedTableName +' svs
        INNER JOIN SignalMetaData smd
            ON smd.SignalId = svs.SignalId  


        INSERT INTO SignalValues SELECT * FROM #sValues

        SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues

        DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

        DROP TABLE #sValues
        DROP TABLE #uniqueIdentifiers

        IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
        BEGIN
            -- processing is completed so drop the table and remvoe the entry
            IF @processingComplete = 1 
            BEGIN 
                DELETE FROM SignalValueJobs WHERE JobId = @currentJob

                IF '''+@currentTable+''' <> ''SignalValues_staging''
                BEGIN
                    DROP TABLE '+ @qualifiedTableName +'
                END
            END
        END 
    '

    EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;

    SET @i = @i - 1
END

DROP TABLE #JobsToProcess

Tôi sử dụng sp_executesqlvì tên bảng cho các bảng phân tầng đến dưới dạng văn bản từ các bản ghi trong bảng công việc.

Quy trình được lưu trữ này chạy cứ sau 2 giây bằng thủ thuật tôi học được từ bài đăng dba.stackexchange.com này .

Vấn đề tôi không thể giải quyết cho cuộc sống của mình là tốc độ mà các phần chèn vào sản xuất được thực hiện. Ứng dụng của tôi tạo các bảng phân tầng tạm thời và điền vào chúng các bản ghi cực kỳ nhanh chóng. Việc chèn vào sản xuất không thể theo kịp số lượng bảng và cuối cùng có thặng dư của hàng nghìn bảng. Cách duy nhất tôi từng có thể theo kịp dữ liệu đến là xóa tất cả các khóa, chỉ mục, các ràng buộc, v.v ... trên SignalValuesbảng sản xuất . Vấn đề tôi phải đối mặt là bảng kết thúc với quá nhiều bản ghi nên không thể truy vấn.

Tôi đã thử phân vùng bảng bằng cách sử dụng [Timestamp]cột phân vùng nhưng không có kết quả. Bất kỳ hình thức lập chỉ mục nào đều làm chậm quá trình chèn đến mức chúng không thể theo kịp. Ngoài ra, tôi cần tạo trước hàng ngàn phân vùng (một phút? Giờ?) Năm trước. Tôi không thể tìm ra cách tạo ra chúng một cách nhanh chóng

Tôi đã thử tạo phân vùng bằng cách thêm một cột được tính vào bảng TimestampMinutecó tên là giá trị của nó, trên INSERT, DATEPART(MINUTE, GETUTCDATE()). Vẫn còn quá chậm.

Tôi đã thử biến nó thành Bảng tối ưu hóa bộ nhớ theo bài viết này của Microsoft . Có thể tôi không hiểu làm thế nào để làm điều đó, nhưng Bộ GTVT đã làm cho phần chèn chậm hơn.

Tôi đã kiểm tra Kế hoạch thực hiện quy trình được lưu trữ và thấy rằng (tôi nghĩ vậy?) Hoạt động chuyên sâu nhất là

SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
    ON smd.SignalId = svs.SignalId

Đối với tôi điều này không có ý nghĩa gì: Tôi đã thêm ghi nhật ký đồng hồ treo tường vào quy trình được lưu trữ đã chứng minh điều khác.

Về mặt ghi nhật ký thời gian, tuyên bố cụ thể ở trên thực hiện trong ~ 300ms trên 100k hồ sơ.

Tuyên bố

INSERT INTO SignalValues SELECT * FROM #sValues

thực hiện trong 2500-3000ms trên hồ sơ 100k. Xóa khỏi bảng các hồ sơ bị ảnh hưởng, mỗi:

DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

mất thêm 300ms.

Làm thế nào tôi có thể làm điều này nhanh hơn? SQL Server có thể xử lý hàng tỷ bản ghi mỗi ngày không?

Nếu có liên quan, đây là SQL Server 2014 Enterprise x64.

Cấu hình phần cứng:

Tôi quên bao gồm phần cứng trong lần đầu tiên của câu hỏi này. Lỗi của tôi.

Tôi sẽ mở đầu điều này bằng các tuyên bố sau: Tôi biết tôi đang mất một số hiệu suất do cấu hình phần cứng của tôi. Tôi đã thử nhiều lần nhưng vì ngân sách, cấp độ C, sự liên kết của các hành tinh, v.v ... không có gì tôi có thể làm để có được một thiết lập tốt hơn không may. Máy chủ đang chạy trên một máy ảo và tôi thậm chí không thể tăng bộ nhớ vì đơn giản là chúng tôi không còn nữa.

Đây là thông tin hệ thống của tôi:

Thông tin hệ thống

Bộ lưu trữ được gắn vào máy chủ VM thông qua giao diện iSCSI vào hộp NAS (Điều này sẽ làm giảm hiệu suất). Hộp NAS có 4 ổ đĩa trong cấu hình RAID 10. Chúng là các ổ đĩa quay 4 WD WD4000FYYZ với giao diện SATA 6GB / s. Máy chủ chỉ có một kho lưu trữ dữ liệu được cấu hình để tempdb và cơ sở dữ liệu của tôi nằm trên cùng một kho dữ liệu.

DOP tối đa bằng không. Tôi có nên thay đổi giá trị này thành giá trị không đổi hay chỉ để SQL Server xử lý? Tôi đã đọc về RCSI: Tôi có đúng không khi cho rằng lợi ích duy nhất từ ​​RCSI đi kèm với các cập nhật hàng? Sẽ không bao giờ có cập nhật cho bất kỳ hồ sơ cụ thể nào, chúng sẽ được chỉnh sửa INSERTvà chỉnh sửa SELECT. RCSI vẫn sẽ có lợi cho tôi chứ?

Tempdb của tôi là 8mb. Dựa trên câu trả lời dưới đây từ jyao, tôi đã thay đổi #sValues ​​thành một bảng thông thường để tránh tempdb hoàn toàn. Hiệu suất là như nhau mặc dù. Tôi sẽ thử tăng kích thước và tốc độ tăng trưởng của tempdb, nhưng với điều kiện là kích thước của #sValues ​​sẽ ít nhiều luôn có cùng kích thước mà tôi không dự đoán được nhiều.

Tôi đã thực hiện một kế hoạch thực hiện mà tôi đã đính kèm dưới đây. Kế hoạch thực hiện này là một lần lặp của bảng phân tầng - bản ghi 100k. Việc thực hiện truy vấn khá nhanh, khoảng 2 giây, nhưng hãy nhớ rằng đây là không có chỉ mục trên SignalValuesbảng và SignalValuesbảng, mục tiêu của INSERT, không có bản ghi trong đó.

Kế hoạch thực hiện


3
Bạn đã thử nghiệm với độ bền chậm trễ?
Martin Smith

2
Những chỉ số nào được đưa ra với chèn sản xuất chậm?
paparazzo

Cho đến nay tôi không nghĩ có đủ dữ liệu ở đây để tìm hiểu những gì thực sự tiêu tốn quá nhiều thời gian. Có phải CPU không? Có phải là IO không? Vì dường như bạn nhận được 30k hàng mỗi giây, nó không giống IO đối với tôi. Tôi có hiểu đúng rằng bạn khá gần đạt được mục tiêu hoàn hảo của mình không? Bạn cần 50k hàng mỗi giây, do đó, một lô 100k cứ sau 2 giây là đủ. Ngay bây giờ một đợt dường như mất 3 giây. Đăng kế hoạch thực hiện thực tế của một đại diện chạy. Bất kỳ đề xuất nào không tấn công các hoạt động tốn thời gian nhất là moot.
usr

Tôi đã đăng kế hoạch thực hiện.
Brandon

Câu trả lời:


7

Tôi đã tính toán rằng, vào lúc cao điểm, số lượng hồ sơ trung bình nằm ở đâu đó trong đại lộ 3-4 tỷ mỗi ngày (20 giờ hoạt động).

Từ ảnh chụp màn hình của bạn, bạn CHỈ có tổng bộ nhớ 8GB RAM và 6 GB được phân bổ cho SQL Server. Đây là cách để giảm bớt những gì bạn đang cố gắng đạt được.

Tôi khuyên bạn nên nâng cấp bộ nhớ lên giá trị cao hơn - 256GB và nâng cấp CPU VM của bạn.

Bạn cần đầu tư vào phần cứng tại thời điểm này cho khối lượng công việc của bạn.

Cũng tham khảo hướng dẫn hiệu suất tải dữ liệu - nó mô tả các cách thông minh để tải dữ liệu hiệu quả.

Tempdb của tôi là 8mb.

Dựa trên chỉnh sửa của bạn .. bạn nên có một tempdb hợp lý - tốt nhất là nhiều tệp dữ liệu tempdb có kích thước bằng nhau cùng với phiên bản kích hoạt TF 1117 và 1118.

Tôi sẽ đề nghị bạn kiểm tra sức khỏe chuyên nghiệp và bắt đầu từ đó.

Rất khuyến khích

  1. Tăng thông số máy chủ của bạn.

  2. Hãy nhờ một người * chuyên nghiệp kiểm tra sức khỏe của cá thể máy chủ cơ sở dữ liệu của bạn và làm theo các khuyến nghị.

  3. Một lần. và B. được thực hiện, sau đó đắm mình vào điều chỉnh truy vấn và tối ưu hóa khác như xem số liệu thống kê chờ, kế hoạch truy vấn, v.v.

Lưu ý: Tôi là một chuyên gia máy chủ sql chuyên nghiệp tại hackhands.com - một công ty đa năng, nhưng không có nghĩa là đề nghị bạn thuê tôi để được giúp đỡ. Tôi chỉ đề nghị bạn thực hiện trợ giúp chuyên nghiệp chỉ dựa trên các chỉnh sửa của bạn.

HTH.


Tôi đang cố gắng đưa ra một đề xuất (đọc: cầu xin) thêm phần cứng cho việc này. Với ý nghĩ đó và câu trả lời của bạn ở đây, không có gì khác từ cấu hình SQL Server hoặc quan điểm tối ưu hóa truy vấn mà bạn muốn đề xuất để thực hiện việc này nhanh hơn?
Brandon

1

Lời khuyên chung cho các vấn đề như vậy với dữ liệu lớn, khi đối mặt với một bức tường và không có gì hoạt động:

Một quả trứng sẽ được nấu khoảng 5 phút. 10 quả trứng sẽ được nấu cùng một lúc nếu đủ điện và nước.

Hay nói cách khác:

Đầu tiên, hãy nhìn vào phần cứng; thứ hai, tách logic quá trình (tu sửa dữ liệu) và thực hiện song song.

Hoàn toàn có thể tạo phân vùng dọc tùy chỉnh một cách linh hoạt và tự động, theo số lượng bảng và mỗi kích thước bảng; Nếu tôi có Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... và tôi không biết hồ sơ của mình ở đâu và có bao nhiêu phân vùng, hãy chạy cùng một truy vấn với tất cả các phân vùng tùy chỉnh trong cùng một thời điểm kết quả được xử lý chuyển tiếp cho logic của tôi.


Vấn đề của OP dường như đang xử lý việc chèn và truy cập vào dữ liệu mới được nhập, hơn là xử lý dữ liệu từ vài tuần hoặc vài tháng trước. OP đề cập đến việc phân vùng dữ liệu theo phút trên dấu thời gian của mình (vì vậy 60 phân vùng, chia dữ liệu hiện tại thành các nhóm riêng biệt); chia theo quý sẽ không có khả năng giúp đỡ nhiều. Quan điểm của bạn được thực hiện tốt nói chung, nhưng không có khả năng giúp đỡ ai đó trong tình huống cụ thể này.
RDFozz

-1

Tôi sẽ thực hiện kiểm tra / tối ưu hóa sau:

  1. Đảm bảo cả dữ liệu và tệp nhật ký của cơ sở dữ liệu sản xuất không phát triển trong quá trình chèn (phát triển trước nếu cần)

  2. Không được dùng

    select * into [dest table] from [source table];

    nhưng thay vào đó, xác định trước [bảng mệnh]. Ngoài ra, thay vì bỏ [bảng định mệnh] và tạo lại nó, tôi sẽ cắt bớt bảng. Bằng cách này, nếu cần, thay vì sử dụng bảng tạm thời, tôi sẽ sử dụng bảng thông thường. (Tôi cũng có thể tạo chỉ mục trên [bảng định mệnh] để tạo thuận lợi cho việc thực hiện truy vấn nối)

  3. Thay vì sử dụng sql động, tôi muốn sử dụng các tên bảng được mã hóa cứng với một số logic mã hóa để chọn bảng nào sẽ hoạt động.

  4. Tôi cũng sẽ theo dõi hiệu suất I / O của bộ nhớ, CPU và đĩa để xem liệu có thiếu tài nguyên trong khối lượng công việc lớn hay không.

  5. Vì bạn đã đề cập, bạn có thể xử lý việc chèn bằng cách bỏ các chỉ mục ở phía sản xuất, tôi sẽ kiểm tra xem có nhiều phân chia trang xảy ra hay không, nếu vậy, tôi sẽ giảm fillfactor của các chỉ mục và xây dựng lại các chỉ mục trước khi tôi xem xét giảm các chỉ số.

Chúc may mắn và yêu câu hỏi của bạn.


Cảm ơn câu trả lời. Tôi đã đặt kích thước cơ sở dữ liệu thành 1gb và tăng thêm 1gb dự đoán rằng các hoạt động tăng trưởng sẽ mất một thời gian, điều đó đã giúp tốc độ ban đầu. Tôi sẽ cố gắng thực hiện sự tăng trưởng trước ngày hôm nay. Tôi đã thực hiện bảng [Dest] như một bảng thông thường nhưng không thấy hiệu suất tăng nhiều. Tôi đã không có nhiều thời gian trong vài ngày qua nhưng tôi sẽ cố gắng đến với những người khác ngày hôm nay.
Brandon
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.