CHỌN LỚN chặn CHỌN


14

Tôi có một vấn đề với một lượng lớn INSERT đang chặn các hoạt động CHỌN của tôi.

Lược đồ

Tôi có một cái bàn như thế này:

CREATE TABLE [InverterData](
    [InverterID] [bigint] NOT NULL,
    [TimeStamp] [datetime] NOT NULL,    
    [ValueA] [decimal](18, 2) NULL,
    [ValueB] [decimal](18, 2) NULL
    CONSTRAINT [PrimaryKey_e149e28f-5754-4229-be01-65fafeebce16] PRIMARY KEY CLUSTERED 
    (
        [TimeStamp] DESC,
        [InverterID] ASC
    ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF
    , IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON)
)

Tôi cũng có quy trình trợ giúp nhỏ này, cho phép tôi chèn hoặc cập nhật (cập nhật về xung đột) bằng lệnh MERGE:

CREATE PROCEDURE [InsertOrUpdateInverterData]
    @InverterID bigint, @TimeStamp datetime
    , @ValueA decimal(18,2), @ValueB decimal(18,2)
AS
BEGIN
    MERGE [InverterData] AS TARGET
        USING (VALUES (@InverterID, @TimeStamp, @ValueA, @ValueB))
        AS SOURCE ([InverterID], [TimeStamp], [ValueA], [ValueB])
        ON TARGET.[InverterID] = @InverterID AND TARGET.[TimeStamp] = @TimeStamp
    WHEN MATCHED THEN
        UPDATE
        SET [ValueA] = SOURCE.[ValueA], [ValueB] = SOURCE.[ValueB]              
    WHEN NOT MATCHED THEN
        INSERT ([InverterID], [TimeStamp], [ValueA], [ValueB]) 
        VALUES (SOURCE.[InverterID], SOURCE.[TimeStamp], SOURCE.[ValueA], SOURCE.[ValueB]);
END

Sử dụng

Bây giờ tôi đã chạy các phiên bản dịch vụ trên nhiều máy chủ thực hiện cập nhật lớn bằng cách gọi [InsertOrUpdateInverterData]thủ tục nhanh chóng.

Ngoài ra còn có một trang web thực hiện các truy vấn CHỌN trên [InverterData]bảng.

Vấn đề

Nếu tôi thực hiện các truy vấn CHỌN trên [InverterData]bảng thì chúng được tiến hành trong các khoảng thời gian khác nhau, phụ thuộc vào việc sử dụng INSERT của các phiên bản dịch vụ của tôi. Nếu tôi tạm dừng tất cả các phiên bản dịch vụ thì CHỌN nhanh như chớp, nếu cá thể đó thực hiện thao tác chèn nhanh thì các CHỌN sẽ rất chậm hoặc thậm chí là hết thời gian chờ.

Nỗ lực

Tôi đã thực hiện một số CHỌN trên [sys.dm_tran_locks]bàn để tìm các quy trình khóa, như thế này

SELECT
tl.request_session_id,
wt.blocking_session_id,
OBJECT_NAME(p.OBJECT_ID) BlockedObjectName,
h1.TEXT AS RequestingText,
h2.TEXT AS BlockingText,
tl.request_mode

FROM sys.dm_tran_locks AS tl

INNER JOIN sys.dm_os_waiting_tasks AS wt ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.partitions AS p ON p.hobt_id = tl.resource_associated_entity_id
INNER JOIN sys.dm_exec_connections ec1 ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections ec2 ON ec2.session_id = wt.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

Đây là kết quả:

nhập mô tả hình ảnh ở đây

S = Chia sẻ. Phiên tổ chức được cấp quyền truy cập chung vào tài nguyên.

Câu hỏi

Tại sao các CHỌN bị chặn bởi [InsertOrUpdateInverterData]thủ tục chỉ sử dụng các lệnh MERGE?

Tôi có phải sử dụng một số loại giao dịch với chế độ cách ly được xác định bên trong [InsertOrUpdateInverterData]không?

Cập nhật 1 (liên quan đến câu hỏi từ @Paul)

Dựa trên báo cáo nội bộ của máy chủ MS-SQL về [InsertOrUpdateInverterData]thống kê sau:

  • Thời gian CPU trung bình: 0,12ms
  • Quá trình đọc trung bình: 5,76 mỗi / s
  • Quá trình ghi trung bình: 0,4 mỗi / s

Dựa vào điều này, có vẻ như lệnh MERGE hầu như bận rộn với các hoạt động đọc sẽ khóa bảng! (?)

Cập nhật 2 (liên quan đến câu hỏi từ @Paul)

Các [InverterData]bảng như đã sau số liệu thống kê lưu trữ:

  • Dung lượng dữ liệu: 26.901,86 MB
  • Số hàng: 131.827.749
  • Phân vùng: đúng
  • Số lượng phân vùng: 62

Dưới đây là tập kết quả sp_WhoIsActive hoàn chỉnh (nhất) :

SELECT chỉ huy

  • dd hh: mm: ss.mss: 00 00: 01: 01.930
  • phiên_id: 73
  • Wait_info: (12629ms) LCK_M_S
  • CPU: 198
  • chặn_session_id: 146
  • đọc: 99.368
  • viết: 0
  • tình trạng: bị đình chỉ
  • open_tran_count: 0

[InsertOrUpdateInverterData]Lệnh chặn

  • dd hh: mm: ss.mss: 00 00: 00: 00.330
  • phiên_id: 146
  • chờ đợi
  • CPU: 3.972
  • chặn_session_id: NULL
  • đọc: 376,95
  • viết: 126
  • tình trạng: đang ngủ
  • open_tran_count: 1

Các ([TimeStamp] DESC, [InverterID] ASC)ngoại hình giống như một sự lựa chọn kỳ lạ đối với nhóm chỉ số. Tôi có nghĩa là DESCmột phần.
ypercubeᵀᴹ

Tôi hiểu quan điểm của bạn: Chỉ mục cụm dữ liệu chèn dữ liệu DESC sẽ buộc xây dựng lại bảng thay vì nối thêm vào ... con chó hiệu suất; sẽ khóa bảng trong khi việc xây dựng lại xảy ra ... có. bởi Jove, bạn có nó Cấu trúc là nguyên nhân cho khóa nhiều hơn khóa.
Alocyte

Câu trả lời:


12

Đầu tiên, mặc dù hơi không liên quan đến câu hỏi chính, nhưng MERGEtuyên bố của bạn có nguy cơ xảy ra lỗi do điều kiện cuộc đua . Vấn đề, tóm lại, là có thể nhiều luồng đồng thời kết luận rằng hàng đích không tồn tại, dẫn đến các nỗ lực va chạm để chèn. Nguyên nhân sâu xa là không thể lấy khóa chia sẻ hoặc cập nhật trên một hàng không tồn tại. Giải pháp là thêm một gợi ý:

MERGE [dbo].[InverterData] WITH (SERIALIZABLE) AS [TARGET]

Các serializable mức cô lập gợi ý đảm bảo phạm vi then chốt nơi hàng sẽ đi bị khóa. Bạn có một chỉ mục duy nhất để hỗ trợ khóa phạm vi, vì vậy gợi ý này sẽ không có tác động bất lợi đến việc khóa, bạn chỉ cần có được sự bảo vệ chống lại điều kiện cuộc đua tiềm năng này.

Câu hỏi chính

Tại sao SELECTsbị chặn bởi thủ tục [insertOrUpdateInverterData] chỉ sử dụng MERGEcác lệnh?

Trong mức cách ly đã đọc cam kết mặc định, khóa chia sẻ (S) được lấy khi đọc dữ liệu và thông thường (mặc dù không phải lúc nào) được phát hành ngay sau khi đọc xong. Một số khóa chia sẻ được giữ đến cuối tuyên bố.

Một MERGEcâu lệnh sửa đổi dữ liệu, do đó, nó sẽ thu được các khóa S hoặc cập nhật (U) khi định vị dữ liệu cần thay đổi, được chuyển thành các khóa độc quyền (X) ngay trước khi thực hiện sửa đổi thực tế. Cả hai khóa U và X phải được giữ đến cuối giao dịch.

Điều này đúng trong tất cả các mức cô lập ngoại trừ cách ly ảnh chụp nhanh 'lạc quan' (SI) - không bị nhầm lẫn với phiên bản đọc đã cam kết, còn được gọi là cách ly ảnh chụp nhanh đã cam kết (RCSI).

Không có gì trong câu hỏi của bạn cho thấy một phiên chờ khóa S bị chặn bởi phiên giữ khóa U. Những ổ khóa này là tương thích . Bất kỳ chặn nào gần như chắc chắn được gây ra bằng cách chặn trên khóa X bị giữ. Điều này có thể hơi khó nắm bắt khi một số lượng lớn các khóa ngắn hạn đang được thực hiện, chuyển đổi và phát hành trong một khoảng thời gian ngắn.

Lệnh open_tran_count: 1trên insertOrUpdateInverterData đáng để nghiên cứu. Mặc dù lệnh đã không chạy rất lâu, nhưng bạn nên kiểm tra xem bạn không có giao dịch có chứa (trong ứng dụng hoặc thủ tục được lưu trữ ở cấp độ cao hơn) không cần thiết lâu. Thực hành tốt nhất là giữ các giao dịch càng ngắn càng tốt. Điều này có thể không có gì, nhưng bạn chắc chắn nên kiểm tra.

Giải pháp tiềm năng

Như Kin đã đề xuất trong một nhận xét, bạn có thể tìm cách kích hoạt mức cô lập phiên bản hàng (RCSI hoặc SI) trên cơ sở dữ liệu này. RCSI được sử dụng thường xuyên nhất, vì nó thường không yêu cầu nhiều thay đổi ứng dụng. Khi được bật, mức cô lập cam kết đọc mặc định sử dụng các phiên bản hàng thay vì lấy khóa S để đọc, do đó, chặn SX bị giảm hoặc loại bỏ. Một số thao tác (ví dụ kiểm tra khóa ngoài) vẫn thu được khóa S theo RCSI.

Tuy nhiên, hãy lưu ý rằng các phiên bản hàng tiêu thụ không gian tempdb, nói chung tỷ lệ thuận với tốc độ hoạt động thay đổi và thời lượng giao dịch. Bạn sẽ cần kiểm tra việc triển khai của mình một cách kỹ lưỡng dưới tải để hiểu và lập kế hoạch cho tác động của RCSI (hoặc SI) trong trường hợp của bạn.

Nếu bạn muốn bản địa hóa việc sử dụng phiên bản của mình, thay vì kích hoạt nó cho toàn bộ khối lượng công việc, SI vẫn có thể là một lựa chọn tốt hơn. Bằng cách sử dụng SI cho các giao dịch đọc, bạn sẽ tránh được sự tranh chấp giữa người đọc và người viết, với chi phí người đọc thấy phiên bản của hàng trước khi bất kỳ sửa đổi đồng thời nào bắt đầu (chính xác hơn, thao tác đọc trong SI sẽ luôn thấy trạng thái cam kết của hàng tại thời điểm giao dịch SI bắt đầu). Có rất ít hoặc không có lợi ích gì khi sử dụng SI cho các giao dịch bằng văn bản, bởi vì khóa ghi vẫn sẽ được thực hiện và bạn sẽ cần xử lý mọi xung đột ghi. Trừ khi đó là những gì bạn muốn :)

Lưu ý: Không giống như RCSI (đã được bật một lần áp dụng cho tất cả các giao dịch đang chạy đã đọc cam kết), SI phải được yêu cầu rõ ràng bằng cách sử dụng SET TRANSACTION ISOLATION SNAPSHOT;.

Các hành vi tinh vi phụ thuộc vào độc giả chặn các nhà văn (bao gồm cả mã kích hoạt!) Làm cho việc kiểm tra trở nên thiết yếu. Xem loạt bài viết được liên kết của tôi và Sách trực tuyến để biết chi tiết. Nếu bạn quyết định về RCSI, hãy chắc chắn xem xét Sửa đổi dữ liệu trong phần Cách ly ảnh chụp đã cam kết cụ thể.

Cuối cùng, bạn nên đảm bảo cá thể của bạn được vá vào Gói dịch vụ SQL Server 2008 4.


0

Khiêm tốn, tôi sẽ không sử dụng hợp nhất. Tôi sẽ đi với IF Exists (CẬP NHẬT) ELSE (INSERT) - bạn có một khóa được nhóm với hai cột bạn đang sử dụng để xác định các hàng để kiểm tra dễ dàng.

Bạn đề cập đến việc chèn MASSIVE và thực hiện từng bước 1 ... đã nghĩ đến việc gộp dữ liệu trong một bảng phân tầng và sử dụng bộ dữ liệu SQL POWER TOTWHELMING để thực hiện nhiều hơn 1 lần cập nhật / chèn? Giống như có một thử nghiệm thông thường cho nội dung trong bảng phân tầng và giành lấy 10000 đầu mỗi lần thay vì 1 lần ...

Tôi sẽ làm một cái gì đó như thế này trong bản cập nhật của tôi

DECLARE @Set TABLE (StagingKey, ID,DATE)
INSERT INTO @Set
UPDATE Staging 
SET InProgress = 1
OUTPUT StagingKey, Staging.ID, Staging.Date
WHERE InProgress = 0
AND StagingID IN (SELECT TOP (100000) StagingKey FROM Staging WHERE inProgress = 0 ORDER BY StagingKey ASC ) --FIFO

DECLARE @Temp 
INSERT INTO @TEMP 
UPDATE [DEST] SET Value = Staging.Value [whatever]
OUTPUT INSERTED.ID, DATE [row identifiers]
FROM [DEST] 
JOIN [STAGING]
JOIN [@SET]; 
INSERT INTO @TEMP 
INSERT [DEST] 
SELECT
OUTPUT INSERT.ID, DATE [row identifiers] 
FROM [STAGING] 
JOIN [@SET] 
LEFT JOIN [DEST]

UPDATE Staging
SET inProgress = NULL
FROM Staging 
JOIN @set
ON @Set.Key = Staging.Key
JOIN @temp
ON @temp.id = @set.ID
AND @temp.date = @set.Date

Bạn có thể có thể chạy nhiều công việc xuất hiện các đợt cập nhật và bạn cần một công việc riêng biệt đang chạy một thao tác xóa nhỏ giọt

while exists (inProgress is null) 
delete top (100) from staging where inProgress is null 

để dọn dẹp bàn dàn.

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.