Почему SQL Server 2008 блокирует SELECT для длинных транзакций INSERT?
Мы пытаемся иметь транзакционную таблицу, которая только принимает новые записи, вставленные на регулярной основе.
Эта простая таблица требует от нас постоянного добавления к ней новых записей с течением времени. Ожидается, что объем транзакций в этой таблице будет довольно высоким, а также может быть периодический импорт транзакций ( > 1000), который может занять несколько секунд.
Из этих данных мы затем выполняем набор операторов выбора, группирующих разные столбцы, чтобы вернуть требуемые значения.
Из нашего первоначального тестирования мы обнаружили, что узкое место должно быть связано с SQL Server, которое блокирует наш SELECT, когда находится в середине транзакции INSERTS.
Ниже приведен простой пример, который можно запустить, чтобы проиллюстрировать проблему.
- Простая таблица DB
create table LOCK_TEST (
LOCK_TEST_ID int identity ,
AMOUNT int);
- Запустить это в 1 окне запроса
begin tran
insert into LOCK_TEST (AMOUNT) values (1);
WAITFOR DELAY '00:00:15' ---- 15 Second Delay
insert into LOCK_TEST (AMOUNT) values (1);
commit
- В Query 2 выполните параллельное выполнение
select SUM(AMOUNT)
from LOCK_TEST;
Я ожидаю, что Query 2 вернется сразу, с 0 до завершения запроса 1, а затем покажет 2. Мы никогда не хотим видеть 1, возвращенный из второго запроса.
Ответ, который мы рассмотрели, связан с WITH (NOLOCK) в инструкции select. Но это нарушает транзакционные границы, и возвращаемая информация может носить финансовый характер, и мы не хотим видеть какие-либо детали в наших запросах.
Моя проблема, кажется, на стороне INSERT...
Почему INSERT блокирует оператор SELECT, даже если он не изменяет существующие данные?
Вопрос о бонусных точках: Является ли это "особенностью" SQL Server, или мы найдем это на других атрибутах базы данных?
UPDATE
Теперь у меня было время найти локальную базу данных оракула и выполнить тот же простой тест. Этот тест проходит, как и следовало ожидать.
Т.е. я могу запускать запрос так часто, как хочу, и он будет возвращать значение null до момента совершения первой транзакции, а затем возвращает 2.
Есть ли способ заставить SQL Server работать так? или нам нужно перейти к Oracle?
Ответы
Ответ 1
это поведение блокировки является особенностью SQL Server.
С 2005 года и выше вы можете использовать управление версиями на уровне строк (это то, что используется по умолчанию для Oracle) для достижения того же результата, а не для блокировки ваш выбор. Это накладывает дополнительную нагрузку на tempdb, потому что tempdb поддерживает управление версиями на уровне строк, поэтому убедитесь, что вы приспособились к этому.
Чтобы заставить SQL вести себя так, как вы хотите, выполните следующее:
ALTER DATABASE MyDatabase
SET ALLOW_SNAPSHOT_ISOLATION ON
ALTER DATABASE MyDatabase
SET READ_COMMITTED_SNAPSHOT ON
Ответ 2
Это полностью стандартное поведение в SQL Server и Sybase (по крайней мере).
Для изменения данных (вставка, обновление, удаление) требуется эксклюзивные блокировки. это заставляет читателей блокироваться:
С исключительной (X) блокировкой нет других транзакции могут изменять данные; читать операции могут выполняться только с использование подсказки NOLOCK или чтение уровень незафиксированной изоляции.
С SQL Server 2005 и выше они представили изоляцию моментальных снимков (например, управление версиями строк). От MS BOL: Понимание уровней изоляции на основе версий на уровне версий. Это означает, что у читателя есть последние данные, но если вы затем захотите написать назад, скажем, тогда вы можете обнаружить, что данные неверны, поскольку они были изменены в транзакции блокировки.
Вот почему наилучшей практикой является сокращение транзакций. Например, обрабатывайте только то, что вам нужно, между BEGIN/COMMIT, не отправляйте электронные сообщения от триггеров и т.д.
Edit:
Блокировка, как это происходит, происходит в SQL Server. Однако слишком долго блокировка блокируется, что снижает производительность. Слишком долго субъективно, очевидно.
Ответ 3
Это также существует в MySQL. Вот логика этого: если вы выполняете SELECT для некоторых данных, вы ожидаете, что он будет работать с обновленным набором данных. Именно поэтому INSERT приводит к блокировке на уровне таблицы (то есть, если движок не может выполнять блокировку на уровне строк, например, innodb). Есть способы отключить это поведение, чтобы вы могли делать "грязные чтения", но все они являются программным обеспечением (или даже базовым движком).
Ответ 4
Я также столкнулся с этой проблемой. После исследования казалось, что данные не были заблокированы сами по себе, а кластерный индекс, который был изменен в результате вставки. Поскольку кластеризованный индекс находится на страницах данных, связанные строки данных также блокируются.