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:
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_executesql
vì 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 SignalValues
bả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 TimestampMinute
có 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:
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 INSERT
và 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 SignalValues
bảng và SignalValues
bảng, mục tiêu của INSERT
, không có bản ghi trong đó.