Какие изменения необходимо внести в архитектуру блокировки SQL Server, чтобы сделать ее более удобной для разработчиков? - PullRequest
1 голос
/ 26 февраля 2010

В последнее время я сталкиваюсь с довольно разочаровывающей ситуацией, когда SQL-сервер отказывается выдавать блокировки только для первичного ключа, когда против него выполняется выражение, подобное этому select * from table with (rowlock updlock) where key=value. Теперь не поймите меня неправильно, это блокирует строку, но она идет на один шаг дальше и также блокирует таблицу.

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

Я хотел бы видеть добавленный новый lockhint suck as PKLock (который будет означать блокировку первичного ключа), который будет выдавать блокировку первичного ключа row и всякий раз, когда для получения строки используется индекс, сканирование таблицы или другой метод, он проверяет эту блокировку и выполняет ее вместо блокировки всей таблицы.

Поскольку такая блокировка таблицы не требуется, это значительно увеличит возможности параллельного выполнения кода для БД.

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

EDIT

@ Рем

Если я выполню этот запрос

begin transaction
select lockname from locks  where lockname='A'
update Locks Set locked=1 where lockname='A'

, а затем этот запрос:

begin transaction
select lockname from locks  where lockname='A'

Строка A возвращается в обоих примерах до совершения транзакций. Это чтение за обновлением, а не блокировка.

Успешное решение должно делать следующее без указания используемых индексов:

  1. С помощью запроса 1: чтение и блокировка A, обновление A
  2. С помощью запроса 2: чтение и блокировка B, обновление B, запрос подтверждения 2
  3. С запросом 2: читать B и блокироваться до снятия блокировки на A
  4. С запросом 1: совершить запрос 1
  5. С помощью запроса 2: чтение и блокировка A, обновление A, запрос подтверждения 2

Ответы [ 2 ]

3 голосов
/ 27 февраля 2010

Вы уже задавали этот вопрос и получили ответ: исправьте свою схему и свой код . В этом посте конфликт блокировок был блокировкой IX, а конфликт блокировок намерений указывает на блокировки с высокой степенью детализации, что, в свою очередь, указывает на сканирование таблицы. Вам не нужны подсказки блокировки, вам нужен только индекс и достойные запросы. Возьмем, к примеру, ваш другой вопрос: Почему блокировка на уровне строк не работает правильно на SQL-сервере? , где ответ тривиален: таблицу блокировок необходимо организовать по кластерному индексу LockName:

CREATE TABLE [dbo].[Locks]( 
    [LockName] [varchar](50) NOT NULL, 
    [Locked] [bit] NOT NULL, 
    CONSTRAINT [PK_Locks] PRIMARY KEY CLUSTERED  ([LockName]));
GO    

insert into Locks (LockName, Locked) values ('A', 0);
insert into Locks (LockName, Locked) values ('B', 0);
GO

На одном занятии сделайте это:

begin transaction
update Locks 
  set Locked=1 
  output inserted.*
  where LockName = 'A';

На другом сеансе сделайте это:

begin transaction
update Locks 
  set Locked=1 
  output inserted.*
  where LockName = 'B';

Нет конфликта обновлений, нет блокировки, нет необходимости (неправильных) подсказок, ничего. Просто хорошая оле 'правильная схема и дизайн запроса.

В качестве примечания: описанная здесь блокировка уже существует и называется блокировкой клавиш. Это стандартный неявный режим работы SQL Server. Как, по-вашему, в мире SQL Server может публиковать показатели производительности TPC-C, равные 16000 транзакций в секунду? У вас есть все возможности параллелизма на сервере, вам просто нужно прочитать одну или две книги, чтобы понять, как их использовать. Существует множество литературы по этому вопросу, которую можно начать с Обработка транзакций: концепции и методы .

Обновлено

begin transaction 
select lockname from locks  where lockname='A' 
update Locks Set locked=1 where lockname='A'

Это никогда не сработает, независимо от того, сколько / разных подсказок блокировки вы пытаетесь. Вот почему у вас есть обновление с синтаксисом вывода:

begin transaction 
update Locks 
 Set locked=1 
 output inserted.*
 where lockname='A'

это гарантирует, что вы сначала обновите, а затем вернете то, что обновили. Этот метод довольно распространен в базах данных именно для той семантики, которую вы ищете: получение ресурсов. Фактически эта техника является краеугольным камнем дочернего плаката получения ресурсов: обработка очереди. См. Queues параграф в OUTPUT Clause . В очередях у вас есть таблица ресурсов для обработки, и каждый поток захватывает ее, блокирует и запускает обработку:

create table Resources (
   id int identity(1,1) not null,
   enqueue_time datetime not null default getutcdate(),
   is_processing bit not null default 0,
   payload xml);

create clustered index cdxResources on Resources 
  (is_processing, enqueue_time);
go   

-- enqueue:
insert into Resources (payload) values ('<do>This</do>');
insert into Resources (payload) values ('<do>That</do>');
insert into Resources (payload) values ('<do>Something</do>');
insert into Resources (payload) values ('<do>Anything</do>');

Теперь из отдельных сеансов запустите это:

--dequeue
begin transaction;
with cte as (
  select top(1) *
  from Resources with(readpast)
  where is_processing = 0
  order by enqueue_time)
update cte
  set is_processing = 1
  output inserted.*;   

Вы увидите, что каждая сессия захватывает свой собственный ресурс, блокирует его и пропускает все, что заблокировано всеми остальными. Так получилось, что у меня в работе работает система, которая работает точно так же, с более чем 5M ресурсами в таблице (это запросы на обработку платежей через веб-сервис), и выгрузкой из очереди и обработкой около 50 в секунду из 100 параллельных процессоров (занимает около 2 секунд). за вызов к процессу). На кусок ненужного оборудования. Так что это абсолютно возможно.

0 голосов
/ 26 февраля 2010

Возможно, вы вообще не хотите указывать блокировку?

http://blogs.msdn.com/davidlean/archive/2009/04/06/sql-server-nolock-hint-other-poor-ideas.aspx

...