|
Эта статья посвящена анализу работы механизма MS SQL Server
2000, обеспечивающего автоматическое разбиение диапазонов
полей identity для участвующих в репликации слиянием таблиц на
издателе и подписчиках. Суть решаемой этим механизмом проблемы
состоит в том, что значения identity могут устанавливаться и
издателем и подписчиком самостоятельно. Часто, такие поля, как
identity используются в качестве внешнего ключа. Если
тиражировать эти значения средствами репликации слиянием,
может произойти нарушение ограничения внешнего ключа.
Одинаковые значения, полученные на разных серверах, после
слияния вызовут возникновение конфликта. Традиционно, проблема
identity в репликации решается путём использования для этого
опции NOT FOR REPLICATION и разделения его значений на не
пересекающиеся диапазоны для разных база данных. Ещё одним,
кардинальным путём решения этой проблемы является замена
identity на uniqueidentifier. Существует и стандартное
решение, позволяющее доверить разделение диапазонов MS SQL
Server, выступающему в роли издателя. Информация об
автоматически выделяемых диапазонах хранится в таблице
MSrepl_identity_range базы данных distribution. 1. Включить опцию автоматического разделения диапазонов при
создании публикации. Это можно сделать в Create Publication
Wizard или используя хранимую процедуру
sp_addmergearticle. Нужно учитывать, что назначение нового диапазона для
каждого подписчика происходит в рамках сеанса репликации. Т.е.
если агент репликации слиянием не работает в непрерывном
режиме, новый диапазон будет установлен только при успешном
запуске агента. Пороговое значение должно учитывать то, что
если агент запускается редко, значений выделенного диапазона
должно быть достаточно для вставки максимально возможного
количества записей, которое вы прогнозируете в интервале
запуска агента репликации. Введение порогового значения
позволяет иметь резерв на тот случай, когда значения
выделенного диапазона закончатся между сеансами репликации. Он
нужен что бы операции вставки, т.е. выделение следующего
значения identity в рамках установленного диапазона, не
заканчивались ошибкой из-за исчерпания диапазона, который
может быть назначен только при следующем сеансе
репликации. /***** Начало скрипта *****/ use master GO -- База для издателя CREATE DATABASE [identitytest] GO -- База для первого подписчика CREATE DATABASE [subscriber1] GO -- База для второго подписчика CREATE DATABASE [subscriber2] GO -- Создание таблиц SET QUOTED_IDENTIFIER ON GO SET ANSI_NULLS ON GO use [identitytest] GO -- Таблица для издания CREATE TABLE [dbo].[1testtable] ( [idrows] [int] IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL , [dbname] [char] (20) NULL , [rowguid] uniqueidentifier ROWGUIDCOL NOT NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[1testtable] WITH NOCHECK ADD CONSTRAINT [PK_1testtable] PRIMARY KEY CLUSTERED ([rowguid]) ON [PRIMARY] GO ALTER TABLE [dbo].[1testtable] ADD CONSTRAINT [DF_1testtable_rowguid] DEFAULT (newid()) FOR [rowguid] GO -- Таблица аудита значений IDENTITY CREATE TABLE [dbo].[2audittable] ( [idrows] [int] IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL , [dbname] [char] (20) NULL , [rowguid] [uniqueidentifier] NOT NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[2audittable] ADD CONSTRAINT [PK_2audittable] PRIMARY KEY NONCLUSTERED ([rowguid]) ON [PRIMARY] GO -- Триггер, заполняющий таблицу аудита CREATE TRIGGER [NEWROW] ON dbo.[1testtable] FOR INSERT NOT FOR REPLICATION AS INSERT INTO [2audittable] (rowguid, dbname) SELECT rowguid, dbname FROM inserted GO -- Таблицы для подписчиков полностью повторяют таблицы на издателе use [subscriber1] GO CREATE TABLE [dbo].[1testtable] ( [idrows] [int] IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL , [dbname] [char] (20) NULL , [rowguid] uniqueidentifier ROWGUIDCOL NOT NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[1testtable] WITH NOCHECK ADD CONSTRAINT [PK_1testtable] PRIMARY KEY CLUSTERED ([rowguid]) ON [PRIMARY] GO ALTER TABLE [dbo].[1testtable] ADD CONSTRAINT [DF_1testtable_rowguid] DEFAULT (newid()) FOR [rowguid] GO CREATE TABLE [dbo].[2audittable] ( [idrows] [int] IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL , [dbname] [char] (20) NULL , [rowguid] [uniqueidentifier] NOT NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[2audittable] ADD CONSTRAINT [PK_2audittable] PRIMARY KEY NONCLUSTERED ([rowguid]) ON [PRIMARY] GO CREATE TRIGGER [NEWROW] ON dbo.[1testtable] FOR INSERT NOT FOR REPLICATION AS INSERT INTO [2audittable] (rowguid, dbname) SELECT rowguid, dbname FROM inserted GO use [subscriber2] GO CREATE TABLE [dbo].[1testtable] ( [idrows] [int] IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL , [dbname] [char] (20) NULL , [rowguid] uniqueidentifier ROWGUIDCOL NOT NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[1testtable] WITH NOCHECK ADD CONSTRAINT [PK_1testtable] PRIMARY KEY CLUSTERED ([rowguid]) ON [PRIMARY] GO ALTER TABLE [dbo].[1testtable] ADD CONSTRAINT [DF_1testtable_rowguid] DEFAULT (newid()) FOR [rowguid] GO CREATE TABLE [dbo].[2audittable] ( [idrows] [int] IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL , [dbname] [char] (20) NULL , [rowguid] [uniqueidentifier] NOT NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[2audittable] ADD CONSTRAINT [PK_2audittable] PRIMARY KEY NONCLUSTERED ([rowguid]) ON [PRIMARY] GO CREATE TRIGGER [NEWROW] ON dbo.[1testtable] FOR INSERT NOT FOR REPLICATION AS INSERT INTO [2audittable] (rowguid, dbname) SELECT rowguid, dbname FROM inserted GO SET QUOTED_IDENTIFIER OFF GO /***** Конец скрипта *****/ Теперь приступим к организации репликации в рамках одного сервера между тремя базами данных. Для этого, в начале, нужно назначить издателя/дистрибутора. В представленных ниже примерах, использовался сервер с именем DBA. Вместо этого имени Вы будете указывать имя компьютера, на котором установлен Ваш тестовый сервер. На первом рисунке, средствами мастера "Configure Publishing, Subscribers, and Distribution" указываем сервер DBA, как дистрибутора.
![]() После того, как сервер определён дистрибутором, в том же мастере указываем этот же сервер издателем.
![]() После того, как мастер успешно закончит свою работу, в окне мастера должны появиться две новые закладки, Publication Databases и Subscribers. Если это произошло, необходимо разрешить издание базы данных identitytest, как это показано на следующем рисунке:
![]() Следующим шагом, необходимо разрешить серверу DBA быть подписчиком для нашего сервера - издателя. Для этого, необходимо перейти на закладку Subscribers и пометить сервер DBA, как подписчика.
![]() После успешного завершения указанных выше операций, приступим к созданию публикации. Для этого воспользуемся мастером Create Publication Wizard:
![]() В рамках этой статьи будут представлены не все шаги
мастера. Те из шагов, где применимы предлагаемые мастером по
умолчанию значения или опции, представлены здесь не
будут.
![]() Указываем, что у нас будет репликация слиянием.
![]() В качестве издаваемой статьи для нашей публикации указываем таблицу 1testtable и нажимаем на кнопку с многоточием, обведённую на нижеследующем рисунке красным овалом.
![]() В раскрывшемся окне, переходим на закладку Identity Range и устанавливаем размеры диапазонов для издателя и подписчиков равными 10. В нашем случае, заведомо очень маленькое значение размеров диапазонов выбрано исключительно из соображений наглядности следующих ниже тестовых примеров. Порог переключения мы также изменим со стандартного 80% на 90%.
![]() Для полноты понимания, давайте рассмотрим, что означают расположенные на этой закладке объекты: Automatically assign and maintain a unique identity range
for each subscription - опция, которая заставляет SQL Server
назначать диапазоны identity издателем и для каждой подписки
выбранной статьи. Обратите внимание, что изменения значений, показанных на этой закладке, затрагивает все статьи, основанные на этой таблице. Все статьи для всех публикаций, которые основаны на этой таблице, будут использовать эти же свойства диапазона identity. После завершения создания публикации, не забудьте включить логин, от имени которого стартует Ваш MSSQLServer и его агента или логин, через который будет работать репликация, в Publication Access List. Сделать это можно в свойствах публикации, как показано на следующем рисунке.
![]() После того, как публикация настроена, можно приступить к
созданию подписок. Мы создадим две подписки, для разнообразия,
первая будет push, а вторая pull.
![]() В качестве базы данных для push подписки выбираем subscriber1.
![]() Для простоты демонстрации работы примеров, включим агента слияния в непрерывный режим работы.
![]() Поскольку у нас ещё пустые таблицы и они абсолютно идентичны, от первоначальной инициализации можно отказаться.
![]() После успешного завершения мастером создания первой подписки, создаём вторую, pull подписку, воспользовавшись для этого Pull Subscription Wizard, в котором необходимо указать созданную нами ранее публикацию.
![]() Базой данных назначения в этом случае будет subscriber2.
![]() Как и при организации первой подписки, от первоначальной инициализации отказываемся.
![]() Агента слияния запускаем в непрерывном режиме.
![]() После успешного создания мастерами публикации и подписок,
можно приступать к моделированию автоматического назначения
диапазонов identity. Предварительно убедитесь, что задания для
агента слияния запущены и работают в непрерывном режиме без
ошибок. /***** Начало скрипта *****/ use [identitytest] GO declare @count_insert int SET @count_insert = 0 WHILE @count_insert < 20 BEGIN SET @count_insert = (@count_insert + 1) PRINT 'Вставляется ' + CONVERT(varchar(4), @count_insert) + '-я строка' insert [1testtable] (dbname) VALUES ('identitytest') END GO exec sp_adjustpublisheridentityrange identitytest GO /***** Конец скрипта *****/ Обратите внимание на последние сообщения, выдаваемые в окне Results Panel: Вставляется 20-я строка Server: Msg 548, Level 16, State 2, Line 1 The identity range managed by replication is full and must be updated by a replication agent. The INSERT conflict occurred in database 'identitytest', table '1testtable', column 'idrows'. Sp_adjustpublisheridentityrange can be called to get a new identity range. Во первых, настораживает то, что успешно было вставлено 19
записей, хотя размер диапазона для издателя был нами
установлен в 10 записей. Практика показывает, что при
заполнении впервые установленного диапазона identity для
издателя, реально допускается вставка удвоенного диапазона
издателя за минусом одной записи. Синтаксис процедуры следующий: sp_adjustpublisheridentityrange [ @publication = ]
'publication' Для других типов репликации (транзакционной и моментальными
снимками) возможно автоматическое выделение диапазонов и для
издателя, которое происходит при работе Log Reader Agent, и
для подписчиков, которое происходит при работе Distribution
Agent. Поскольку в репликации слиянием не используется Log
Reader Agent, а распределение выполняется Merge Agent,
автоматизация выделения нового диапазона для издателя
полностью ложится на плечи администратора баз
данных. /***** Начало скрипта *****/ use [subscriber1] GO declare @count_insert int SET @count_insert = 0 WHILE @count_insert < 20 BEGIN SET @count_insert = (@count_insert + 1) PRINT 'Вставляется ' + CONVERT(varchar(4), @count_insert) + '-я строка' insert [1testtable] (dbname) VALUES ('subscriber1') END GO /***** Конец скрипта *****/ Для полноты картины, нам осталось заполнить тестовыми данными таблицу третьего подписчика. Выполните и этот скрипт. /***** Начало скрипта *****/ use [subscriber2] GO declare @count_insert int SET @count_insert = 0 WHILE @count_insert < 20 BEGIN SET @count_insert = (@count_insert + 1) PRINT 'Вставляется ' + CONVERT(varchar(4), @count_insert) + '-я строка' insert [1testtable] (dbname) VALUES ('subscriber2') END GO /***** Конец скрипта *****/ Как Вы видите из представленных в окне Results Panel
сообщений, заполнение таблицы на втором подписчике происходит
так же, как и у первого, т.е. вставляется на одну запись
меньше, чем величина диапазона. Можно было бы ожидать, что
диапазон будет изменён издателем при достижении порогового
значения, но это не происходит потому, что даже при запуске
агента слияния (Merge Agent) в непрерывном режиме, он
подключается к издателю дискретно. А поскольку мы вставляем
записи очень быстро, агент не успевает договориться с
издателем о выделении нового диапазона. Наш эксперимент можно
повторить, назначив для агента репликации расписание запуска
или переведя его в режим ручного запуска (отсутствие
расписания для задания, запускающего агента слияния). Если мы
будем вставлять 9 записей и после этого запускать сеанс
репликации агента слияния, диапазон будет изменён. /***** Начало скрипта
*****/ В результате исполнения скрипта мы получим таблицу, сгруппированную по базам данных и отсортированную по identity. В первой колонке будут значения identity, полученные при автоматическом выделении диапазонов издателем. Во второй колонке будут значения identity из таблицы аудита, которые устанавливались без автоматического выделения диапазонов, как это происходит обычным образом, без репликации. В третьей колонке располагаются имена баз данных.
Выводы При использовании в схеме репликации слиянием механизма автоматического выделения диапазонов для полей identity необходимо учитывать следующие важные моменты: 1. Администратор баз данных должен отслеживать заполнение
выделенного для издателя диапазона и обеспечить его
своевременное переключение вызовом процедуры
sp_adjustpublisheridentityrange. |
Автор: Александр Гладченко 2003г. |