SQL Сервер принудительно работает только RowLock - PullRequest
3 голосов
/ 28 февраля 2020

Я столкнулся с большой проблемой при обновлении строк таблицы, в которой хранятся счетчики. Я использую транзакцию, чтобы получить значение и обновить его, и я пытаюсь заблокировать только строку затронутого счетчика, чтобы избежать блокировок и тупиков, но это не работает.

Это сокращенный пример кода, где я могу воспроизвести ошибку:

CREATE TABLE _COUNTERS_ 
(
    ID INT IDENTITY NOT NULL, 
    CODE VARCHAR(20) NOT NULL,
    CVALUE INT NOT NULL DEFAULT 0,
    CONSTRAINT PK_ID PRIMARY KEY CLUSTERED 
    (
        ID ASC,
        CODE ASC,
        CVALUE ASC
    ) WITH (ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = OFF)
)

INSERT INTO _COUNTERS_ (CODE, CVALUE) VALUES ('C1', 0)
INSERT INTO _COUNTERS_ (CODE, CVALUE) VALUES ('C2', 0)

Я пытаюсь избежать PageLocks с определением индекса.

В SQL Server Management Studio, я выполняю это утверждение в окне. Я использую первый запрос, чтобы заблокировать СЧЕТЧИК, чтобы избежать других потоков на серверах (приложение является многопоточным и установлено на ферме, поэтому я не могу использовать. NET блокировки), получая неверное число num, пока оно близко к обновлению:

-- Window 1
BEGIN TRAN 
SELECT *
FROM _COUNTERS_ this_ WITH (
        UPDLOCK,
        ROWLOCK
        )
WHERE this_.CODE = 'C1'
UPDATE _COUNTERS_ SET CVALUE = 1 WHERE ID = 1

В другом окне SQL Server Management Studio я запрашиваю заблокированные ресурсы:

-- Window 2
SELECT  L.request_session_id AS SPID,
--        DB_NAME(L.resource_database_id) AS DatabaseName,
        O.Name AS LockedObjectName,
        P.object_id AS LockedObjectId,
        L.resource_type AS LockedResource,
        L.request_mode AS LockType,
        ST.text AS SqlStatementText,       
--        ES.login_name AS LoginName,
--        ES.host_name AS HostName,
        TST.is_user_transaction as IsUserTransaction,
        AT.name as TransactionName,
        CN.auth_scheme as AuthenticationMethod
FROM    sys.dm_tran_locks L
        JOIN sys.partitions P ON P.hobt_id = L.resource_associated_entity_id
        JOIN sys.objects O ON O.object_id = P.object_id
        JOIN sys.dm_exec_sessions ES ON ES.session_id = L.request_session_id
        JOIN sys.dm_tran_session_transactions TST ON ES.session_id = TST.session_id
        JOIN sys.dm_tran_active_transactions AT ON TST.transaction_id = AT.transaction_id
        JOIN sys.dm_exec_connections CN ON CN.session_id = ES.session_id
        CROSS APPLY sys.dm_exec_sql_text(CN.most_recent_sql_handle) AS ST
WHERE   resource_database_id = db_id()
ORDER BY L.request_session_id

Locked resources

В В третьем окне я выполняю следующую инструкцию:

-- Window 3
BEGIN TRAN 
SELECT *
FROM _COUNTERS_ this_ WITH (
        UPDLOCK,
        ROWLOCK
        )
WHERE this_.CODE = 'C2'
UPDATE _COUNTERS_ SET CVALUE = 2 WHERE ID = 2

И первый выбор продолжает ждать, пока я не совершу первую транзакцию.

Можно ли сохранить все счетчики полностью изолированными при выборе и обновлении?

Некоторые дополнительные соображения о реальной среде:

  • SQL Server 2012
  • В таблице 107 строк
  • Первая транзакция (Окно 1) показывает только одну строку в запросе блокировок, в отличие от двух строк скриншота, который я опубликовал.
  • Вторая транзакция (окно 3) может выполнить первый выбор без ожидания, и она продолжает ожидать оператора UPDATE.
  • Воспроизводится взаимоблокировка (только в реальной среде) при выполнении сначала двух запросов выбора, а затем двух обновлений, он не может быть воспроизведен с опубликованным примером, потому что первый выбор блокирует полную таблицу.

ОБНОВЛЕНИЕ 2020-03-02 Использование индекса по коду (как прокомментировано @larny или отправленный @esat) решает проблему на примере, который я выложил, но в моей реальной таблице (VISUALSEGCONTADORES) выбор не использует новый индекс:

CREATE INDEX ix_VSGCONTADORES ON VISUALSEGCONTADORES (VSC_ALIAS ASC) WITH (ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = OFF);

Здесь p lan одного из выбранных (другой использует тот же индекс): https://www.brentozar.com/pastetheplan/?id=BJRWtocE8

И структура реальной таблицы (со специальным кластеризованным индексом):

CREATE TABLE [dbo].[VISUALSEGCONTADORES](
    [VSC_Id] [int] IDENTITY(1,1) NOT NULL,
    [VSC_Alias] [varchar](30) NOT NULL,
    [VSC_Objeto] [int] NOT NULL,
    [VSC_Serie] [varchar](5) NULL,
    [VSC_Contador] [decimal](20, 8) NOT NULL,
    [VSC_Enabled] [tinyint] NOT NULL,
    [USR_Id_FC] [int] NOT NULL,
    [USR_Id_FM] [int] NOT NULL,
    [VSC_FC] [datetime] NOT NULL,
    [VSC_FM] [datetime] NOT NULL,
    [LOG_ID_FC] [varchar](255) NULL,
    [LOG_ID_FM] [varchar](255) NULL,
    [LOG_FC] [datetime] NULL,
    [LOG_FM] [datetime] NULL,
    [OFI_ID] [int] NULL,
    [VSC_OFICODE] [int] NOT NULL,
    [TRN_Aud_Id_FC] [int] NULL,
    [TRN_Aud_Id] [int] NULL,
 CONSTRAINT [PK_VISUALSEGCONTADORES] PRIMARY KEY CLUSTERED 
(
    [VSC_Alias] ASC,
    [VSC_OFICODE] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = OFF) ON [PRIMARY]
) ON [PRIMARY]

Реальные запросы следующие:

-- Window 1
BEGIN TRAN 
SELECT *
FROM VISUALSEGCONTADORES this_ WITH (
        UPDLOCK,
        ROWLOCK
        )
WHERE this_.VSC_Alias = 'VSG_UltimoCodEXP' AND VSC_OFICODE = 0
UPDATE VISUALSEGCONTADORES SET VSC_Contador = 9910 WHERE VSC_Id = 142

Другое окно

-- Window 3
BEGIN TRAN 
SELECT *
FROM VISUALSEGCONTADORES this_ WITH (
        UPDLOCK,
        ROWLOCK
        )
WHERE this_.VSC_Alias = 'VSG_ULTIMAMATRIZ' AND VSC_OFICODE = 0

UPDATE VISUALSEGCONTADORES SET VSC_Contador = 1273 WHERE VSC_Id = 121

Определяется бизнесом, иногда запросы выполняются с VSC_OfiCode, а иногда нет, но я проверял оба и выкидывал одинаковые результаты.

Заблокированные ресурсы: enter image description here

1 Ответ

1 голос
/ 28 февраля 2020

Запрос Window3 пытается получить эксклюзивную блокировку строки, которая все еще читается Window1 . Если вы проанализируете выполнение следующего запроса.

SELECT *
FROM _COUNTERS_ this_ WITH (
        UPDLOCK,
        ROWLOCK
        )
WHERE this_.CODE = 'C2'

enter image description here

Таким образом, этот запрос будет ожидать фиксации или отката запроса Window1. Если вы попытаетесь выполнить обновление Window3 по отдельности, оно будет выполнено

-- Window 3
BEGIN TRAN 
--SELECT *
--FROM _COUNTERS_ this_ WITH (
--        UPDLOCK,
--        ROWLOCK
--        )
--WHERE this_.CODE = 'C2'
UPDATE _COUNTERS_ SET CVALUE = 2 WHERE ID = 2

С другой стороны, если мы решим эту проблему, мы можем создать некластеризованный индекс для столбца CODE или мы можем изменить запрос, как показано ниже

--Window3
BEGIN TRAN 
SELECT *
FROM _COUNTERS_ this_ WITH (
        UPDLOCK,
        ROWLOCK
        )
WHERE this_.CODE = 'C2' AND ID=2
UPDATE _COUNTERS_ SET CVALUE = 2 WHERE ID = 2

Этот два параметра позволяют избежать сканирования индекса кластера

...