Mam problem z ogromną liczbą INSERTów, które blokują moje operacje SELECT.
Schemat
Mam taki stół:
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)
)
Mam również tę małą procedurę pomocniczą, która pozwala mi wstawiać lub aktualizować (aktualizować w przypadku konfliktu) za pomocą polecenia 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
Stosowanie
Teraz uruchomiłem instancje usług na wielu serwerach, które wykonują masowe aktualizacje [InsertOrUpdateInverterData]
, szybko wywołując procedurę.
Istnieje również strona internetowa, która wykonuje zapytania SELECT na [InverterData]
stole.
Problem
Jeśli wybiorę zapytania w [InverterData]
tabeli, będą one wykonywane w różnych przedziałach czasowych, w zależności od użycia INSERT moich wystąpień usługi. Jeśli zatrzymam wszystkie wystąpienia usługi, SELECT jest błyskawicznie szybki, jeśli wystąpienie wykonuje szybkie wstawianie, SELECT staje się naprawdę wolny, a nawet czas oczekiwania zostaje anulowany.
Próbowanie
Skończyłem kilka instrukcji SELECT na [sys.dm_tran_locks]
stole, aby znaleźć procesy blokowania, takie jak to
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
Oto wynik:
S = wspólny. Sesja wstrzymująca ma wspólny dostęp do zasobu.
Pytanie
Dlaczego SELECTY są blokowane przez [InsertOrUpdateInverterData]
procedurę, która używa tylko komend MERGE?
Czy muszę korzystać z jakiejś transakcji ze zdefiniowanym trybem izolacji w środku [InsertOrUpdateInverterData]
?
Aktualizacja 1 (związana z pytaniem z @Paul)
Oprzyj się na wewnętrznych raportach serwera MS-SQL o [InsertOrUpdateInverterData]
następujących statystykach:
- Średni czas pracy procesora: 0,12 ms
- Średnie procesy odczytu: 5,76 na sekundę
- Średnie procesy zapisu: 0,4 na sekundę
Na tej podstawie wygląda na to, że polecenie POŁĄCZ jest głównie zajęte operacjami odczytu, które zablokują tabelę! (?)
Aktualizacja 2 (związana z pytaniem z @Paul)
[InverterData]
Stół jak ma następujące statystyki przechowywania:
- Przestrzeń danych: 26 901,86 MB
- Liczba wierszy: 131 827 749
- Partycjonowany: prawda
- Liczba partycji: 62
Oto (prawie) kompletny zestaw wyników sp_WhoIsActive :
SELECT
Komenda
- dd hh: mm: ss.mss: 00 00: 01: 01.930
- session_id: 73
- wait_info: (12629ms) LCK_M_S
- Procesor: 198
- blocking_session_id: 146
- brzmi: 99,368
- pisze: 0
- status: zawieszony
- open_tran_count: 0
[InsertOrUpdateInverterData]
Polecenie blokowania
- dd hh: mm: ss.mss: 00 00: 00: 00.330
- session_id: 146
- wait_info: NULL
- Procesor: 3972
- blocking_session_id: NULL
- brzmi: 376,95
- pisze: 126
- status: spanie
- open_tran_count: 1
([TimeStamp] DESC, [InverterID] ASC)
wygląda dziwny wybór dla indeksu klastrowego. Mam na myśli tęDESC
część.