Проверить наличие изменений в таблице SQL Server?

Как я могу контролировать базу данных SQL Server на предмет изменений в таблице, не используя триггеры или каким-либо образом изменяя структуру базы данных? Я предпочитаю среду программирования .NET и C#.

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

Под «изменениями в таблице я подразумеваю изменения данных таблицы, а не изменения структуры таблицы.

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


С учетом моих требований (отсутствие триггеров или модификации схемы, SQL Server 2000 и 2005) лучшим вариантом действий, по-видимому, является использование BINARY_CHECKSUM функции в T-SQL . Я планирую реализовать следующее:

Каждые X секунд запускайте следующий запрос:

SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*))
FROM sample_table
WITH (NOLOCK);

И сравните это с сохраненным значением. Если значение изменилось, просмотрите таблицу строка за строкой, используя запрос:

SELECT row_id, BINARY_CHECKSUM(*)
FROM sample_table
WITH (NOLOCK);

И сравните возвращенные контрольные суммы с сохраненными значениями.

Ответов (8)

Решение

Взгляните на команду КОНТРОЛЬНАЯ СУММА:

SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM sample_table WITH (NOLOCK);

Это будет возвращать одно и то же число при каждом запуске, пока содержимое таблицы не изменилось. См. Мой пост об этом для получения дополнительной информации:

КОНТРОЛЬНАЯ СУММА

Вот как я использовал его для восстановления зависимостей кеша при изменении таблиц:
Зависимость кеша базы данных ASP.NET 1.1 (без триггеров)

Как часто вам нужно проверять наличие изменений и насколько велики (с точки зрения размера строки) таблицы в базе данных? Если вы воспользуетесь CHECKSUM_AGG(BINARY_CHECKSUM(*)) методом, предложенным Джоном, он просканирует каждую строку указанной таблицы. NOLOCK Подсказка помогает, но на большой базе данных, вы все еще ударять каждую строку. Вам также нужно будет сохранить контрольную сумму для каждой строки, чтобы вы знали, что она изменилась.

Вы думали о том, чтобы взглянуть на это под другим углом? Если вы не хотите изменять схему для добавления триггеров (что имеет смысл, это не ваша база данных), рассматривали ли вы возможность работы с поставщиком приложения, который создает базу данных?

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

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

Здесь дикая догадка: если вы не хотите изменять таблицы сторонних разработчиков, можете ли вы создать представление, а затем установить триггер для этого представления?

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

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

Иметь задание DTS (или задание, которое запускается службой Windows), которое выполняется с заданным интервалом. Каждый раз при запуске он получает информацию о данной таблице с помощью системных таблиц INFORMATION_SCHEMA и записывает эти данные в репозиторий данных. Сравните данные, возвращенные относительно структуры таблицы, с данными, возвращенными в предыдущий раз. Если он другой, значит, вы знаете, что структура изменилась.

Пример запроса для возврата информации обо всех столбцах в таблице ABC (в идеале перечисление только столбцов из таблицы INFORMATION_SCHEMA, которые вы хотите, вместо использования * select **, как я здесь):

select * from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'ABC'

Вы будете отслеживать различные столбцы и представления INFORMATION_SCHEMA в зависимости от того, как именно вы определяете «изменения в таблице».

К сожалению, я не думаю, что в SQL2000 есть простой способ сделать это. Если вы сузите свои требования до SQL Server 2005 (и более поздних версий), тогда вы в деле. Вы можете использовать SQLDependency класс в System.Data.SqlClient . См. Уведомления о запросах в SQL Server (ADO.NET) .

К сожалению, CHECKSUM не всегда работает должным образом для обнаружения изменений .

Это только примитивная контрольная сумма и не вычисление циклического избыточного кода (CRC).

Поэтому вы не можете использовать его для обнаружения всех изменений, например, симметричные изменения приводят к одной и той же КОНТРОЛЬНОЙ СУММЕ!

E. g. решение с CHECKSUM_AGG(BINARY_CHECKSUM(*)) всегда будет давать 0 для всех 3 таблиц с разным содержимым:


SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM 
(
  SELECT 1 as numA, 1 as numB
  UNION ALL
  SELECT 1 as numA, 1 as numB
)  q
-- delivers 0!

SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM ( SELECT 1 as numA, 2 as numB UNION ALL SELECT 1 as numA, 2 as numB ) q -- delivers 0!

SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM ( SELECT 0 as numA, 0 as numB UNION ALL SELECT 0 as numA, 0 as numB ) q -- delivers 0!