Как явно заблокировать таблицу в Microsoft SQL Server (ищите взломать - неработающий клиент) - PullRequest
8 голосов
/ 12 июля 2011

Это был мой первоначальный вопрос:

Я пытаюсь выяснить, как обеспечить принудительную блокировку таблиц EXCLUSIVE в SQL Server.Мне нужно работать с неработающими читателями (вне моего контроля, с закрытым исходным кодом), которые явно устанавливают для своего уровня ИЗОЛЯЦИИ значение READ UNCOMMITTED.В результате, независимо от того, сколько блокировок и какую изоляцию я указываю при выполнении вставки / обновления, клиенту просто нужно установить правильную изоляцию и вернуться к чтению моего текущего мусора.

Ответ оказался довольно простым -

, хотя нет никакой возможности вызвать явную блокировку, любое изменение DDL вызывает блокировку, которую я искал.

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

ВЫИГРЫШ!

#!/usr/bin/env perl

use Test::More;

use warnings;
use strict;

use DBI;

my ($dsn, $user, $pass) = @ENV{ map { "DBICTEST_MSSQL_ODBC_$_" } qw/DSN USER PASS/ };

my @coninf = ($dsn, $user, $pass, {
  AutoCommit => 1,
  LongReadLen => 1048576,
  PrintError => 0,
  RaiseError => 1,
});

if (! fork) {
  my $reader = DBI->connect(@coninf);
  $reader->do('SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');

  warn "READER $$: waiting for table creation";
  sleep 1;

  for (1..5) {
    is_deeply (
      $reader->selectall_arrayref ('SELECT COUNT(*) FROM artist'),
      [ [ 0 ] ],
      "READER $$: does not see anything in db, sleeping for a sec " . time,
    );
    sleep 1;
  }

  exit;
}

my $writer = DBI->connect(@coninf);

eval { $writer->do('DROP TABLE artist') };
$writer->do('CREATE TABLE artist ( name VARCHAR(20) NOT NULL PRIMARY KEY )');
$writer->do(do('DISABLE TRIGGER _lock_artist ON artist');

sleep 1;

is_deeply (
  $writer->selectall_arrayref ('SELECT COUNT(*) FROM artist'),
  [ [ 0 ] ],
  'No rows to start with',
);

$writer->begin_work;

$writer->prepare("INSERT INTO artist VALUES ('bupkus') ")->execute;
# this is how we lock
$writer->do('ENABLE TRIGGER _lock_artist ON artist');
$writer->do('DISABLE TRIGGER _lock_artist ON artist');

is_deeply (
  $writer->selectall_arrayref ('SELECT COUNT(*) FROM artist'),
  [ [ 1 ] ],
  'Writer sees inserted row',
);

# delay reader
sleep 2;

$writer->rollback;

# should not affect reader
sleep 2;

is_deeply (
  $writer->selectall_arrayref ('SELECT COUNT(*) FROM artist'),
  [ [ 0 ] ],
  'Nothing committed (writer)',
);

wait;

done_testing;



РЕЗУЛЬТАТ:

READER 27311: waiting for table creation at mssql_isolation.t line 27.
ok 1 - READER 27311: does not see anything in db, sleeping for a sec 1310555569
ok 1 - No rows to start with
ok 2 - Writer sees inserted row
ok 2 - READER 27311: does not see anything in db, sleeping for a sec 1310555571
ok 3 - READER 27311: does not see anything in db, sleeping for a sec 1310555572
ok 3 - Nothing committed (writer)
ok 4 - READER 27311: does not see anything in db, sleeping for a sec 1310555573
ok 5 - READER 27311: does not see anything in db, sleeping for a sec 1310555574

Ответы [ 3 ]

6 голосов
/ 12 июля 2011

Один из способов сделать это состоит в том, чтобы вызвать на столе операцию, которая блокирует SCH-M на таблице, что предотвратит чтение по таблице даже на уровне изоляции READ UNCOMMITTED. Например, выполнение такой операции, как ALTER TABLE REBUILD (возможно, на определенном пустом разделе, чтобы уменьшить влияние на производительность) в рамках вашей операции, предотвратит весь параллельный доступ к таблице до момента фиксации.

5 голосов
/ 12 июля 2011

Добавьте подсказку блокировки к вашему SELECT:

SELECT COUNT(*) FROM artist WITH (TABLOCKX)

и поместите INSERT в транзакцию.

Если ваше первоначальное утверждение находится вявная транзакция, SELECT будет ожидать блокировки перед обработкой.

4 голосов
/ 12 июля 2011

Нет прямого способа принудительной блокировки, когда соединение находится на уровне изоляции READ UNCOMMITTED.

Решением было бы создание представлений для читаемых таблиц, которые предоставляют подсказку READCOMMITTED. Если вы контролируете имена таблиц, используемые читателем, это может быть довольно просто. В противном случае у вас будет довольно тяжелая работа, так как вам придется либо изменять авторов для записи в новые таблицы, либо создавать INSTEAD OF INSERT/UPDATE триггеры для представлений.

Edit:

Майкл Фредриксон прав, указав, что представление, просто определенное как выборка из базовой таблицы с табличной подсказкой, не требует обновления определений триггера. Если вы переименуете существующие проблемные таблицы и замените их представлениями, сторонний клиент не должен быть мудрее.

...