Определение изменений строки с момента последнего доступа в SQL Server - PullRequest
4 голосов
/ 28 сентября 2010

У нас есть многопользовательская система, пользователи которой сохраняются в центральной базе данных SQL Server 2005. Мы столкнулись с проблемой, когда пользователь обновляет изменения из базы данных, в то время как другой пользователь сохраняет новые данные. В настоящее время мы собираем изменения, так как у нас есть столбец отметки времени в каждой таблице, который заполняется при каждой вставке / обновлении строки. У другого пользователя будет храниться временная метка на клиенте, которая является последним разом, когда он извлекал данные из базы данных.

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

  • Пользователь1 начинает сохранение, открывает транзакцию и вставляет / изменяет строки, изменяя их временные метки.
  • Пользователь2 обновляется из базы данных до того, как Пользователь1 зафиксировал изменения, что приводит к обновлению временной метки Пользователя2.
  • Пользователь 1 фиксирует транзакцию и все изменения.
  • Пользователь2 снова обновляется из базы данных, однако, поскольку его временная метка была обновлена ​​ранее, только вторая половина изменений была зафиксирована пользователем1 и вызвала ошибки, вызывающие сбой приложения.

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

Дополнительный пример

  • Пользователь1 начинает сохранение, открывает транзакцию и вставляет / изменяет строки и обновляет их временные метки.
  • Пользователь2 запускает другое сохранение, открывает транзакцию, вставляет / изменяет ДРУГИЕ строки, обновляя свои временные метки, и фиксирует свою транзакцию.
  • Пользователь3 обновляется из базы данных и извлекает все данные, которые Пользователь2 зафиксировал, обновляя свою метку LastRefreshTimestamp до последней отметки времени, созданной в БД Пользователем2.
  • Пользователь1 совершает свою транзакцию.
  • Пользователь 3 снова обновляется из базы данных, но извлекает все изменения между концом транзакции User2 и концом транзакции User1 на основе его LastRefreshTimestamp, пропуская все изменения, зафиксированные транзакцией User1 до начала транзакции User2.

Ответы [ 8 ]

8 голосов
/ 05 августа 2011

Интересная проблема, и я не могу придумать простого чистого решения на основе T-SQL, но это именно тот тип задачи синхронизации, для которого было создано отслеживание изменений в SQL 2008 ... http://msdn.microsoft.com/en-us/library/bb933875.aspx

Хороший общий обзор отслеживания изменений и сбора данных об изменениях в этом блоге / статье: http://blogs.technet.com/b/josebda/archive/2009/03/24/sql-server-2008-change-tracking-ct-and-change-data-capture-cdc.aspx

И вы можете объединить это с Microsoft Sync Framework, если ваша общая цель - сохранить клиентакопии хранилища: http://msdn.microsoft.com/en-us/sync/bb887608

2 голосов
/ 09 августа 2011

Я предполагаю, что отметка времени - это двоичный тип строки (8).Это увеличивается по любой старой причине: если вы делаете update-.. set col1 = col1.., например,

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

Допустим, пользователь5 загружает данные, но пользователь 4 изменяет , а затем возвращает (в конце концов, они являются конечными пользователями ...), у вас будет 2 изменения метки времени, но те же базовые данные.

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

Мы решили это с помощью

  • , а не , используя временные метки (они увеличиваются с фиктивными обновлениями)
  • с использованием изоляции REPEATABLE_READ
  • каждое сохранение отправляет также старые значения
  • , сравнивая каждое значение строки, подлежащей обновлению

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

Если вы можете запретить ОБНОВЛЕНИЯ, когда данные фактически не изменились, так что отметка времени не сработает, тогда вам это не нужно.

Вы можете добавить дополнительные столбцы, отслеживающие фиктивное обновление, если хотите, чтобы пользователь "сохранял", даже если это не "настоящее" обновление, но это изменяет значения обращения к строке

Следовательно, наше решениевыше ...

2 голосов
/ 07 августа 2011

Мне кажется, я понимаю ваш вопрос:

Каждый клиент поддерживает отключенный набор данных и периодически обновляет свою локальную копию набора данных.

Вы используете отметку времени SQL-92 (которая отличается от отметки времени SQL Server; одна - это дата-время, другая - двоичная версия строки) для отслеживания обновлений, чтобы вы могли найти дельту в наборе данных и обновитьклиентов.

При таком подходе возникают проблемы, поскольку ваши метки времени рассчитываются до того, как транзакция будет полностью зафиксирована, а последующие транзакции вычисляют более новые метки времени, но могут фиксироваться до того, как более старые транзакции и ваша «последняя метка времени» пропустят некоторые или все записи.

Так что же можно сделать?

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

Вот несколько решений, которые могут работать.

  1. Внесите «погрешность» в свои обновления.Если ваше последнее обновление было 2011-08-06 23:14:05, вычтите несколько минут (вам придется выяснить, каков этот предел погрешности) и получите эти обновления.Это было бы вашим быстрым решением для лейкопластыря.Если вы хотите усовершенствовать это решение, используйте временную метку SQL Server (автоматическая двоичная версия строки), вычислите контрольную сумму всех строк и сохраните ее в локальном наборе данных и сравните строки во время обновления.

  2. Ленивый обновляет.Опять же, используйте отметку времени SQL Server (rowversion) для управления версиями строк и проверьте изменения, прежде чем пользователю будет разрешено редактировать, если отметки времени не совпадают, выполните обновление.При проверке используйте подсказку NOWAIT, чтобы определить, редактируется ли строка в данный момент.Обычно вы выполняете эту проверку снова при сохранении, чтобы убедиться, что нет конфликта (http://www.mssqltips.com/tip.asp?tip=1501) это более типичный подход.

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

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

Есть еще одна возможность

Я могу ошибаться в своей интерпретации вашего вопроса.Возможно, вы вычисляете метку времени обновления на клиенте, а затем сохраняете ее на сервере.Если дело обстоит именно так, вместо того, чтобы передавать явную дату-время на сервер, передайте GETDATE ().Это вычислит дату и время на лету.

Если кода слишком много для рефакторинга, вы также можете обработать его с помощью триггера:

Простой пример:

CREATE TRIGGER Trig_MyTable_Update ON dbo.MyTable FOR INSERT, UPDATE
AS 
IF @@ROWCOUNT=0 RETURN 
UPDATE U SET UpdateDT = GETDATE()
FROM MyTable U INNER JOIN INSERTED I ON U.Id = I.Id
1 голос
/ 05 августа 2011

Просмотр триггеров AFTER http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/triggers.htm#CNCPT017, Триггер базы данных - это особый тип хранимой процедуры, который вызывается автоматически при возникновении заранее определенного события, например, пользователь сохраняет новые данные.

USE database
GO
CREATE TRIGGER on_table_update_insert_delete
ON table AFTER update, delete, insert
AS
DECLARE @string AS char(1)

IF EXISTS(SELECT * FROM inserted)
    IF EXISTS(SELECT * FROM deleted)
    SET @string = 'U';
    ELSE
    SET @string = 'I';
    ELSE
    SET @string = 'D'
INSERT INTO event_logs(username, event_type, event_time, table_name)
SELECT SYSTEM_USER @string, getdate(), 'Table'
GO

Для включения триггера вам необходимо ввести этот код

USE database
GO
ENABLE TRIGGER on_table_update_insert_delete ON DATABASE
GO

Спасибо:)

1 голос
/ 29 сентября 2010

Установка временной отметки на стороне клиента на дату / время последнего извлечения в сочетании с отметкой времени во время транзакции на стороне сервера - вот где возникает ваша проблема. Либо выполните последнее «обновление / изменение меток времени всех затронутых записей» в качестве последнего действия в вашей транзакции - хотя вы все равно можете столкнуться с проблемой разрешения меток времени, либо измените логику извлечения для выбора записей на основе различий меток времени между клиентами и сервер вместо сравнения всех отметок времени записи с одной «извлекаемой» датой / временем.

1 голос
/ 28 сентября 2010

Добавить обновление временных меток для любой строки, созданной / обновленной непосредственно перед фиксацией.

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

0 голосов
/ 28 сентября 2010

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

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

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

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

0 голосов
/ 28 сентября 2010

Тип timestamp страдает в некоторых реализациях sql, когда потенциально многократные транзакции происходят во время одной и той же "второй".Для реализаций sql с таймерами высокого разрешения, например, наносекундами, это вряд ли будет проблемой.

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

...