Распараллеливание хэш-соединений

 По материалам статьи Craig Freedman: Parallel Hash Join


SQL Server использует один из двух вариантов стратегии распараллеливания хэш-соединения. Наиболее часто встречается хэш-секционирование (Hash Partitioning). Реже можно встретить Broadcast-секционирование; эту стратегию часто называют “Broadcast hash join”.


Хэш-секционирование


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


 


create table T1 (a int, b int, x char(200))
set nocount on
declare
@i int
set
@i = 0
while @i < 1000000
  begin
    insert
T1 values(@i, @i, @i)
    set @i = @i + 1
  end
select
* into T2 from T1
select * into T3 from T1
select * from T1 join T2 on T1.b = T2.a

  |–Parallelism(Gather Streams)
       |–Hash Match(Inner Join, HASH:([T1].[b])=([T2].[a]), RESIDUAL:([T2].[a]=[T1].[b]))
            |–Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([T1].[b]))
            |    |–Table Scan(OBJECT:([T1]))
            |–Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([T2].[a]))
                 |–Table Scan(OBJECT:([T2]))

Обратите внимание, что в отличие от распараллеленного соединения вложенных циклов (Nested Loops), у нас есть по одному оператору Exchange (оператор параллелизма, обозначенный как “Parallelism”) для каждой ветки плана со сканированием таблиц под хэш-соединением (оба входных потока: компановка и проба). Тут Exchange используется для распределения хэшей по разным потокам хэш-соединения.

Broadcast-секционирование

Рассмотрим, что произойдет, если мы попытаемся распараллелить хэш-соединение с использованием хэш-секционирования, когда у нас не много строк в ветке компановки хэш-соединения. Если строк меньше, чем потоков хэш-соединения, некоторые потоки вообще не смогут получить ни одной строки. В таком случае, такие потоки в фазе пробы показанного выше соединения останутся без работы и будут бездействовать. Даже если строк будет больше, чем потоков, в связи с наличием дубликатов значений ключа и/или перекосов в хэш-функции, одни потоки могут получить намного больше строк, чем другие.
Чтобы исключить риск подобных перекосов, когда оптимизатор считает число строк компановки относительно небольшим, он может выбрать для передачи этих строк все потоки хэш-соединения. Так как все строки компановки транслируются на все хэш-соединения потока, в Broadcast хэш-соединении не имеет значения, куда будут направлены строки пробы. Каждая строка пробы может быть отправлена в любой поток и, если она может участововать в соединении с какой – либо строкой компановки, так и будет.

Вот пример:


select * from T1 join T2 on T1.b = T2.a where T1.a = 0


  |–Parallelism(Gather Streams)
       |–Hash Match(Inner Join, HASH:([T1].[b])=([T2].[a]), RESIDUAL:([T2].[a]=[T1].[b]))
            |–Parallelism(Distribute Streams, Broadcast Partitioning)
            |    |–Table Scan(OBJECT:([T1]), WHERE:([T1].[a]=(0)))
            |–Table Scan(OBJECT:([T2]))

Обратите внимание, что оператор параллелизма расположенный выше оператора простмотра T1 теперь превратился в Broadcast, в то время как полностью исчезло распараллеливание просмотра T2. Нам не нужен параллелизм выше T2, потому что Распараллеленный Просмотр автоматически распределяет страницы и строки T2 потоками хэш-соединения. Это подобно тому, как Распараллеленный Просмотр распределяет строки между потоками Nested Loops Join в случае Распараллеленного Соединения Вложенных Циклов.
Как и в распараллеленном соединении вложенных циклов, если на входе пробы Broadcast хэш-соединения встретится серия значений  (например, какэто бывает, если присутствует оператор TOP), тогда, возможно, для распараллеливания потребуется перераспределение строк в порядке круговой очереди.

И так, если Broadcast хэш-соединениe настолько хороши (они снижают риски перекосов), то почему бы нам не использовать их везде? Закавыка в том, что Broadcast хэш-соединениe использует больше оперативной памяти, чем его коллега хэш-секционирование. Это происходит, поскольку мы предоставляем каждую строку сборки для каждого потока хэш-соединения, и, если использовать вдвое больше потоков, то удвоится и объем нужной для строк сборки памяти. При распараллеленном хэш-соединении с хэш-секционированием будет использоваться одинаковый объём памяти, независимо от степени параллелизма.

Bitmap фильтрация

  |–Parallelism(Gather Streams)
       |–Hash Match(Inner Join, HASH:([T1].[b])=([T2].[a]), RESIDUAL:([T2].[a]=[T1].[b]))
            |–Bitmap(HASH:([T1].[b]), DEFINE:([Bitmap1008]))
            |    |–Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([T1].[b]))
            |         |–Table Scan(OBJECT:([T1]), WHERE:([T1].[a]<(100000)))
            |–Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([T2].[a]), WHERE:(PROBE([Bitmap1008])=TRUE))
                 |–Table Scan(OBJECT:([T2]))

Что представляет собой оператор Bitmap? Предикат “T1.a < 100000″ убирает 90% строк компановки из T1. Он также косвенно убирает 90% строк в выборке из таблицы T2, потому что они не участвуют в соединении со строками из T1. Оператор Bitmap обеспечивает эффективный способ фильтрации T1 непосредственно по T2 без необходимости протаскивания строк по всему пути через оператор параллелизма к соединению. Как следует из названия, этот оператор создаёт битовую карту. Так же, как и в хэш-соединении, берётся хэш каждой строки T1 по ключу соединения T1.b и устанавливается соответствующий бит в битовой карте. По окончании просмотра Т1 будет завершено и хэш-соединение сборки, битовая карта будет передана оператору параллелизма (который видно на плане выше просмотра Т2) где карта используется в качестве фильтра. Тут берётся хэш каждой строки T2 по ключу соединения T2.a и проверяется соответствующий ему бит в битовой карте. Если бит установлен, то строка участвует в соединении и проходит дальше к хэш-соединению. Если бит не установлен, то строка не участвует в соединении и будет отброшена. Более подробную информацию о битовых масках смотрите в этоу статье блога SQL Server Query Processing Team

Распараллеленное соединение вложенных циклов (Nested Loops)

По материалам статьи из блога Craig Freedman: Parallel Nested Loops Join

Перевод Ирины Наумовой

Техническая редакция Александра Гладченко

SQL Server распараллеливает соединение вложенных циклов (Nested Loops Join), распределяя в случайном порядке строки внешней таблицы по потокам вложенных циклов. В данном случае, речь идёт о строках, которые поступают первыми, и мы их видим вверху, на графическом плане запроса. Например, если на входе соединения вложенных циклов имеется два потока, каждый поток получит приблизительно половину строк. Потоки проходятся по строкам внутренней таблицы соединения (то есть, по строкам, поданным во вторую очередь, мы их видим ниже в плане запроса), точно по такому же алгоритму, как это было бы реализовано в сценарии с последовательной обработкой строк. Таким образом, для каждой обрабатываемой потоком строки внешней таблицы, поток обеспечивает соединение своей внутренней таблицы, используя эту строку в качестве источника коррелированных параметров. Это позволяет потокам работать независимо друг от друга. При этом для внутренней таблицы соединения вложенных циклов SQL Server не добавляет операторы параллелизма и работу с ней не распараллеливает.

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

Давайте рассмотрим простой пример. Чтобы вынудить оптимизатор выбрать параллельный план, создадим таблицу с большим числом строк. Если Вы решите проверить эти примеры у себя, учтите, что заполнение таблицы тестовыми данными может занять несколько минут.

create table T1 (a int, b int, x char(200))

 

set nocount on

declare @i int

set @i = 0

while @i < 1000000

  begin

    insert T1 values(@i, @i, @i)

    set @i = @i + 1

  end

 

select * into T2 from T1

select * into T3 from T1

 

create unique clustered index T2a on T2(a)

create unique clustered index T3a on T3(a)

 

select * from T1 join T2 on T1.b = T2.a where T1.a < 100

Rows

Executes

100

1

|–Parallelism(Gather Streams)

100

2

    |–Nested Loops(Inner Join, OUTER REFERENCES:([T1].[b], [Expr1007]) OPTIMIZED)

100

2

           |–Table Scan(OBJECT:([T1]), WHERE:([T1].[a]<(100)))

100

100

           |–Clustered Index Seek(OBJECT:([T2].[T2a]), SEEK:([T2].[a]=[T1].[b]) ORDERED FORWARD)

Сразу заметим, что в плане виден только один оператор Exchange (оператор, указывающий на параллелизм, и обозначенный в этом плане, как Parallelism(Gather Streams)). Так как оператор параллелизма находится в корне плана запроса, все операторы в этом плане (соединение вложенных циклов, просмотр таблицы и поиск по кластерному индексу) будут выполняться в нескольких параллельных потоках.

Индекс на T1 не был создан сознательно. Отсутствие индекса приводит к тому, что для выборки строк будет выполнен просмотр всей таблицы и потом к выборке будет применён предикат с оценкой «T1.a < 100». Поскольку в T1 миллион строк, просмотр таблицы будет дорогостоящей операцией, и поэтому оптимизатор предпочтёт использование распараллеленного просмотра T1.

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

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

Так как этот запрос выполнялся со степенью параллелизма – DOP равным 2, на текстовом плане исполнения запроса мы видим, что в колонке «Executes» для просмотра таблицы и соединения (которые попали в один и тот же поток) стоит значение 2. Кроме того, просмотр с соединением возвращают в общей сложности 100 строк, хотя мы не можем сделать из этого плана вывод, сколько строк возвратил каждый из двух потоков (эту информацию можно получить, используя статистику в виде XML, о будет сказано ниже).

Далее, соединение обращается к внутренней таблице (в этом случае используется поиск по индексу T2), поиск выполняется для каждой из 100 строк, полученных из внешней таблицы. Тут мы имеем дело с маленькой хитростью в понимании представленного выше сценария. Мы видим, что у каждого из двух потоков свой экземпляр поиска по индексу. Также, в плане показано, что поиск по индексу расположен ниже оператора соединения, да и само соединение мы видим ниже оператора параллелизма, но тут не используется распараллеленный просмотр. Вместо просмотра оптимизатор указывает использовать два экземпляра поиска по индексу внутренней таблицы соединения. Эти экземпляры поиска выполняются независимо друг от друга, используя два разных набора строк внешней таблицы и разные коррелированные параметры. Как и в последовательном плане, мы видим 100 исполнений просмотров индекса: по одному для каждой строки внешней таблицы соединения. Независимо от комплектации соединения вложенных циклов со стороны внутренней таблицы, в плане исполнения запроса мы всегда будем видеть выбор последовательного сценария, точно такого же, как это было показано выше в нашем простом примере.

Усложнённый пример

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

select *

from (select top 100 * from T1 order by a) T1top

join T2 on T1top.b = T2.a


|–Parallelism(Gather Streams)
|–Nested Loops(Inner Join, OUTER REFERENCES:([T1].[b], [Expr1007]) WITH UNORDERED PREFETCH)
|–Parallelism(Distribute Streams, RoundRobin Partitioning)
|  |–Top(TOP EXPRESSION:((100)))
|  |–Parallelism(Gather Streams, ORDER BY:([T1].[a] ASC))
|  |–Sort(TOP 100, ORDER BY:([T1].[a] ASC))
|  |–Table Scan(OBJECT:([T1]))
|–Clustered Index Seek(OBJECT:([T2].[T2a]), SEEK:([T2].[a]=[T1].[b]) ORDERED FORWARD)

Основное отличие этого плана от плана из предыдущего примера в том, что последний использует «TOP 100». Выборка первой сотни может получить правильную оценку, только если поток имеет последовательный плана исполнения (тут нет возможности распилить данные для нескольких потоков, поскольку всё может вылиться в очень большое или наоборот, слишком малое число строк разных потоков). Таким образом, у нас добавляется обработка (например, распределяющая данные по потокам), обеспечивающая распараллеливание после TOP. В таких случаях невозможно задействовать распараллеленный просмотр для выборки строк потоков соединения. Вместо этого распараллеливание для этого соединения выполняется посредством «RoundRobin Partitioning» – круговой «дозировки», которая и поставляет строки для потоков соединения.

Возможные проблемы

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

Если в обслуживании запроса задействовано много потоков и выбрано небольшое количество страниц и/или строк, распараллеленный просмотр и круговая «дозировка» могут оказаться бессильны заставить все потоки соединения работать с высокой производительностью. Некоторые потоки могут не получить для себя строк и будут просто простаивать. Эта проблема наиболее заметна в распараллеленном просмотре, когда каждый поток одномоментно выдает несколько страниц, но делает это не так часто, как распараллеливание, которое одномоментно распределяет по одному пакету (что эквивалентно одной странице).

Мы можем наблюдать эту проблему в простом примере выше, анализируя данные XML статистики:

<RelOp NodeId=1 PhysicalOp=Nested Loops LogicalOp=Inner Join …>

<RunTimeInformation>

<RunTimeCountersPerThread Thread=2 ActualRows=0 … />

<RunTimeCountersPerThread Thread=1 ActualRows=100 … />

<RunTimeCountersPerThread Thread=0 ActualRows=0 … />

</RunTimeInformation>

</RelOp>

Все возвращаемые соединением строки обрабатывались потоком 1. Почему? У просмотра таблицы есть остаточный предикат “T1.a <100”. Этот предикат возвращает истину для первых 100 строк в таблице и ложь для остальных строк. Все (три) страницы, содержащие первые 100 строк, направлены в первый поток. Тут не возникает большой проблемы, так как внутренняя сторона соединения обходится довольно дёшево и вносит небольшой процент в суммарную стоимость запроса (по сравнению с просмотром таблицы, который составляет наибольший процент). Однако эта проблема могла бы стать более существенной, если бы внутренняя сторона запроса обходилась заметно дороже. Проблема особенно заметна с секционированными таблицами. О секционированных таблицах мы ещё поговорим в следующих статьях блога, а сейчас иллюстрацию упомянутой тут проблемы можно найти в статье блога SQL Server Development Customer Advisory Team: Partitioned Tables, Parallelism & Performance considerations

Поиск узких мест ввода-вывода для MS SQL Server

По материалам статьи Tibor Nagy: How to Identify I/O Bottlenecks in MS SQL Server – 17.03.2011


Проблема


   Суть проблематики данной статьи – регулярное замедление в работе баз данных SQL Server. После статей, посвящённых анализу использования памяти и CPU, мы хотели бы продолжить исследование причины замедления путём анализа узких мест ввода-вывода.



Решение


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



Performance Monitor


   Чтобы определить загрузку подсистемы ввода-вывода, можно воспользоваться системной утилитой Performance Monitor. Перечисленные ниже счётчики производительности могут оказаться полезны для этих целей:



    PhysicalDisk Object: Avg. Disk Queue Length. Этот счетчик показывает среднее число запросов чтения и записи, которые были поставлены в очередь для указанного физического диска. Чем выше это число, тем большее дисковых операций ожидает ввода-вывода. Если это значение во время пиковой нагрузки на SQL Server частенько превышает двойку, следует задуматься о необходимости принятия адекватных мер. Если используется несколько дисков, показания счётчика нужно разделить на число дисков в массиве и убедиться, не превышает ли результирующее значение число 2. Например, у Вас есть 4 диска и длина очереди диска 10, искомая глубина очереди находится следующим образом: 10/4 = 2,5, это и будет значением, которое нужно анализировать, а не 10.

    Avg. Disk Sec/Read и Avg. Disk Sec/Write показывают среднее время чтения и записи данных на диск. Хорошо, если это значение не превышает 10 ms, но все еще приемлемо, если значение меньше 20 ms. Значения, превышающие этот порог, требуют исследования возможностей оптимизации.

    Physical Disk: %Disk Time – время, которое диск был занят обслуживанием запросов записи или чтения. Это значение должно быть ниже 50%.

    Disk Reads/Sec и Disk Writes/Sec – показатель уровня загруженности диска операциями чтения – записи. Значение должно быть меньше 85% от пропускной способности диска, поскольку при превышении этого порога время доступа увеличивается по экспоненте.
    Пропускную способность диска можно определить постепенно увеличивая нагрузку на систему. Одним из способов определения пропускной способности дисковой подсистемы является использование специализированной утилиты SQLIO. Она позволяет определить ту точку, где пропускная способность перестаёт расти при дальнейшем увеличении нагрузки.


   При выборе конфигураций RAID можно использовать следующие формулы вычисления числа операций ввода-вывода (I/Os), приходящихся на один диск:



    Raid 0: I/O на диск = (чтений + записей) / число дисков массива
    Raid 1: I/O на диск = [чтений + (записей *2)] / 2
    Raid 5: I/O на диск = [чтений + (записей *4)] / число дисков массива
    Raid 10: I/O на диск = [чтений + (записей *2)] / число дисков массива

   Вот пример вычисления количества операций ввода-вывода на диск для RAID 1 на основе значений счетчиков:


    Disk Reads/sec = 90
    Disk Writes/sec = 75
    Формула для ввода-вывода на RAID-1 массив является [чтений + (записей*2)] / 2 или [90 + (75*2)] / 2 = 120 I/Os на диск.


Динамические административные представления


   Есть полезные динамические административные представления (DMV), с помощью которых можно выявить узкие места ввода-вывода.
   Специальный тип ожидания краткой блокировки для операции ввода-вывода (I/O latch) имеет место тогда, когда задача переходит в состояние ожидания завершения кратковременной блокировки буфера, находящегося в состоянии обслуживания запроса ввода-вывода. В зависимости от типа запроса, это приводит к появлению ожиданий с именами PAGEIOLATCH_EX или PAGEIOLATCH_SH. Длительные ожидания могут указывать на проблемы с дисковой подсистемой. Чтобы посмотреть статистику таких ожиданий можно использовать системное представление sys.dm_os_wait_stats. Для того, что бы определить наличие проблем ввода-вывода, нужно посмотреть значения waiting_task_counts и wait_time_ms при нормальной рабочей нагрузке SQL Server и сравнить их со значениями, полученными при ухудшении производительности.



    select * from sys.dm_os_wait_stats
    where wait_type like 'PAGEIOLATCH%'
    ORDER BY wait_type asc


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



    select db_name(database_id),
          file_id,
          io_stall,
          io_pending_ms_ticks,
          scheduler_address
    from sys.dm_io_virtual_file_stats (NULL, NULL) iovfs,
         sys.dm_io_pending_io_requests as iopior
    where iovfs.file_handle = iopior.io_handle


Дисковая фрагментация


   Я рекомендую регулярно проверять уровень фрагментации и конфигурацию дисков, используемых экземпляром SQL Server.
   Фрагментация файлов на разделе NTFS может стать причиной существенной потери производительности. Диски должны регулярно дефрагментироваться. Исследование показывают, что в некоторых случаях диски, подключаемые из сетей SAN, менее производительны, если их файлы дефрагментированы, т.е. эти СХД оптимизированы под случайный ввод-вывод. Прежде чем устранять файловую фрагментацию, стоит выяснить, как она сказывается на производительности работы SAN.
   Фрагментация индексов также может стать причиной повышения нагрузки ввода-вывода на NTFS, но на это влияют уже другие условия, отличные от тех, что существенны для SAN, оптимизированных для случайного доступа.



Конфигурация дисков / Best Practices


   Как правило, для повышения производительности, файлы журналов кладут на отдельные физические диски, а файлы данных размещают на других физических дисках. Ввод-вывод для высоко нагруженных файлов данных (включая tempDB) носит случайный характер. Ввод-вывод для файла журнала транзакций носит последовательный характер, кроме случаев отката транзакций.
   Встроенные в шасси сервера (локальные) диски можно использовать только для файлов журнала транзакций, потому что они хорошо ведут себя при последовательном вводе-выводе, а при случайном вводе-выводе ведут себя плохо.
   Файлы данных и журналов должны размещаться на разных дисковых массивах, у которых используются разные наборы физических дисков. В большинстве случаев, когда решение должно укладываться в не большой бюджет, я рекомендую размещать файл журнала транзакций на массиве RAID1, собранном из локальных дисков. Файлы данных БД лучше разместить на внешней системе хранения в сети SAN, так, чтобы к используемым для данных физическим дискам доступ получал только SQL Server, что позволит контролировать обслуживание его запросов и получать достоверные отчёты загрузки дисковой подсистемы. От подключения дисковых подсистем напрямую к серверу лучше отказаться.
   Кэширование записи должно быть включено везде, где только это возможно, и вы должны удостовериться, что кэш защищен от перебоев в питании и других возможных отказов (независимая батарея подпитки кэша на контроллере).
   Во избежание появления узких мест ввода-вывода для OLTP систем, лучше не смешивать нагрузки, характерные для OLTP и OLAP. Кроме того, удостоверьтесь, что серверный код оптимизирован и, где это необходимо, созданы индексы, которые тоже позволяют избавиться от ненужного ввода-вывода.



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


Распараллеленный Просмотр


По материалам статьи Craig Freedman: Parallel Scan


Перевод Ирины Наумовой и Александра Гладченко



В этой статье я собираюсь рассмотреть то, как SQL Server распараллеливает просмотр таблицы (сканирования – scans). Оператор просмотра – один из немногих операторов, которые адаптированы к параллелизму. Большинство других операторов ничего не знают о параллелизме, и не заботятся о том, выполняются ли они параллельно; оператор просмотра является в этом случае исключением.



Как же в действительности работает распараллеленный просмотр?


Потоки, которые составляют распараллеленный просмотр, сообща трудятся над тем, чтобы выполнить полный просмотр всех строк в таблице. Априори, нет никакого явного закрепления строк или страниц за конкретными потоками. Вместо этого движок хранилища раздаёт страницы потокам динамически. Доступ к страницам таблицы координирует поставщик распараллеленных страниц (parallel page supplier). Он гарантирует, что каждая страница будет отдана только одному потоку и, таким образом, попадёт на обработку только один раз.



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



У этого алгоритма есть пара преимуществ:



     

  • Он независим от числа потоков. Мы можем добавлять и удалять потоки из распараллеленного просмотра, и это будет учитываться автоматически. Если мы удвоим число потоков, каждый поток получит на обработку (приблизительно) вполовину меньше страниц. И, если система ввода-вывода сможет поддержать необходимую производительность, просмотр будет выполнен вдвое быстрее.
  • Он гибок, и может противостоять разбалансировке. Если один поток работает медленнее чем другие потоки, этот поток запросит меньше страниц, в то время как другие, более быстрые потоки, возьмут на себя дополнительную нагрузку. Общее время исполнения будет ухудшаться плавно. (Сравните этот сценарий с тем, что бы произошло, если присваивание страниц потокам было бы статическим: тогда общее время исполнения определял бы самый медленный поток).
  •  


Примеры


Давайте начнём с простого примера. Чтобы получить распараллеленный план, нам понадобится довольно большая таблица; если таблица будет слишком маленькой, то оптимизатор может прийти к заключению, что лучше подходит последовательный план исполнения. Показанный ниже сценарий создаёт таблицу из 1000000 строк, которые (благодаря фиксированной длине столбца char (200)) займут приблизительно 27000 страниц.
Предупреждение: Если Вы решаете выполнить этот пример, учтите, что его исполнение может занять несколько минут, которые понадобятся для заполнения таблицы данными. create table T (a int, x char(200))



    set nocount on
    declare @i int
    set @i = 0
    while @i < 1000000
      begin
         insert T values(@i, @i)
         set @i = @i + 1
      end


После этого, для самого простого запроса:



    select * from T

         |--Table Scan(OBJECT:([T]))


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



    select * from T where a < 1000

         |--Parallelism(Gather Streams)
            |--Table Scan(OBJECT:([T]), WHERE:([T].[a]<CONVERT_IMPLICIT(int,[@1],0)))


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



Балансировка нагрузки


Как я писал выше, алгоритм распараллеленного просмотра выделяет страницы потокам динамически. Мы можем наблюдать это с помощью следующего примера. Рассмотрим запрос, который возвращает все строки таблицы:



    select * from T where a % 2 = 0 or a % 2 = 1


“Хитрый″ предикат запутывает оптимизатор, который неправильно оценивает количество элементов и генерирует параллельный план:



         |--Parallelism(Gather Streams)
            |--Table Scan(OBJECT:([T]), WHERE:([T].[a]%(2)=(0) OR [T].[a]%(2)=(1)))


На SQL Server 2005 используя “SET STATISTICS XML ON” мы можем узнать, сколько строк обрабатывает каждый поток. Вот результирующий XML для двухпроцессорной системы:



    <RelOp NodeId="2" PhysicalOp="Table Scan" LogicalOp="Table Scan" ...>
         <RunTimeInformation>
            <RunTimeCountersPerThread Thread="2" ActualRows="530432" ... />
            <RunTimeCountersPerThread Thread="1" ActualRows="469568" ... />
            <RunTimeCountersPerThread Thread="0" ActualRows="0" ... />
         </RunTimeInformation>
         ...
    </RelOp>


Как видно, оба потока (1 и 2), обрабатывают примерно половину строк. Поток 0 является координатором, или ещё его называют основным потоком. Он выполняет только ту часть плана исполнения запроса, которая выше самого верхнего итератора обмена. Таким образом, мы не ожидаем, что какие либо строки будут обработаны ещё какими-либо операторами с распараллеливанием.
Давайте повторим эксперимент, но теперь выполним одновременно ещё один последовательный запрос. Этот запрос перекрёстного соединения будет работать в течение довольно продолжительного времени (он должен обработать один триллион строк), и будет использовать очень много процессорных циклов:



    select min(T1.a + T2.a) from T T1 cross join T T2 option(maxdop 1)


Такой последовательный запрос использует только один из двух процессоров. Давайте снова выполним другой запрос во время его работы:



    select * from T where a % 2 = 0 or a % 2 = 1

    <RelOp NodeId="2" PhysicalOp="Table Scan" LogicalOp="Table Scan" ...>
         <RunTimeInformation>
            <RunTimeCountersPerThread Thread="1" ActualRows="924224" ... />
            <RunTimeCountersPerThread Thread="2" ActualRows="75776" ... />
            <RunTimeCountersPerThread Thread="0" ActualRows="0" ... />
         </RunTimeInformation>
         ...
    </RelOp>


На этот раз распараллеленный поток с идентификатором 1 обработал больше 90% строк, в то время как поток 2, который был занят исполнением показанного выше запроса с последовательным планом, обработал заметно меньше строк. Распараллеленный просмотр автоматически сбалансировал работу между двумя потоками. Так как у потока 1 было больше свободных циклов (он не конкурировал с последовательным планом), он запросил и просмотрел больше страниц.
Если Вы пробуете воспроизвести этот эксперимент, не забудьте потом уничтожить последовательный запрос! Иначе, он будет продолжать выполняться и тратить впустую процессорное время в течение довольно длительного времени.
Похожая балансировка нагрузки применима в равной мере в тех случаях, когда поток замедляется из-за внешних факторов (наподобие последовательного запроса в нашем примере) или из-за внутренних факторов. Например, если обработка некоторых строк будет обходиться дороже, чем других, то мы также увидим похожее поведение.


 




Оператор распараллеливания (Exchange)


По материалам статьи Craig Freedman «The Parallelism Operator (aka Exchange)»


Перевод Ирины Наумовой


Как я уже писал в статье Введение в распараллеливание исполнения запроса , итератор параллелизма (или обмена – Exchange operator) фактически привносит в процесс выполнения запроса возможность распараллеливания задачи. Оптимизатор помещает оператор обмена в том месте, где происходит разделение на несколько потоков, и оператор обмена перемещает строки между потоками.



Итератор обмена – на самом деле это два итератора


Итератор обмена в действительности является двумя итераторами: производитель и потребитель (этим он отличается от всех других итераторов). Мы размещаем производителя в корень поддерева запроса (его часто называют ветвлением). Производитель считывает строки на входе своего поддерева, транслирует эти строки в пакеты, и направляет пакеты соответствующим потребителям. Потребитель размещается в «листе» следующего поддерева запроса. Потребитель принимает пакеты от своего производителя, извлекает из этих пакетов строки, и возвращает строки родительскому итератору. Например, оператор Repartition Streams, выполняющийся со степенью параллелизма (DOP) равной двойке, будет состоять из двух производителей и двух потребителей:




Обратите внимание, что, в то время как поток данных между большинством итераторов основывается на принципе «тяни» (итераторы вызывают GetRow для своих «детишек», когда готовы к обработке другой строки), поток данных оператора обмена между производителем и потребителем основан на принципе «толкай». То есть производитель заполняет пакет строками и «выталкивает» его потребителю. Эта модель позволяет потокам производителя и потребителя выполняться независимо друг от друга (тут мы имеем такое управление потоком данных, которое не позволит быстрому производителю затопить медленного потребителя большим количеством пакетов).



Сколько существует разных типов обмена?


Оператор обмена может быть классифицирован тремя различными способами.



Во-первых, мы можем классифицировать оператор обмена, основываясь на количестве потоков потребителя и/или производителя:



Тип Количество потоков производителя Количество потоков потребителя
Gather Streams DOP 1
Repartition Streams DOP DOP
Distribute Streams 1 DOP



Оператор Gather Streams часто называют «началом распараллеливания» итератора обмена: т.е. те операторы, которые расположены в плане выше него, выполняются последовательно, а те операторы, которые ниже него, выполняются параллельно. Корнем итератора обмена любого параллельного плана всегда является оператор Gather Streams, так как результаты любого плана запроса должны, в конечном счете, собраться в один поток, который будет возвращен клиенту. Оператор Distribute Streams часто называют «завершением распараллеливания» обмена. Это противоположность предыдущему оператору: те операторы, которые выше Distribute Streams, выполняются параллельно, в то время как те операторы, которые ниже него, работают последовательно.



Во-вторых, мы можем классифицировать оператор обмена между потоками, основываясь на том, как перенаправляются строки от производителя потребителю. Обращаем Ваше внимание на свойство «Тип секционирования (partitioning type)» оператора обмена потоков. Тип секционирования имеет смысл только для оператора Repartition Streams или оператора Distribute Streams, когда у оператора Gather Streams существует только один маршрут для строк: в направлении единственного потока потребителя. SQL Сервер поддерживает следующие типы секционирования:




Тип секционирования Описание
Broadcast Посылает все строки всем потокам потребителя.
Round Robin Последовательно посылает каждый пакет строк следующему потребителю.
Hash Определяет куда посылать каждую строку на основании оценки хэш-функции для одной или более колонки в строке.
Round Robin Range
Demand Посылает следующую строку следующему потребителю, который ее запрашивает. Этот тип секционирования – единственный тип обмена, который использует преимущественно притягивающую, чем выталкивающую модель для потока данных. Он используется только в планах запросов с секционированными таблицами.


В-третьих, мы можем классифицировать обмен как слияние (или сохранение порядка сортировки) и без слияния (или сохранение отсутствия упорядочивания). Потребителю при обмене слиянием гарантируется, что строки от нескольких производителей будут возвращены в заданном порядке сортировки (строки должны быть в этом порядке сортировки уже у производителя; обмен слиянием ничего не сортирует). Обмен слиянием имеет смысл только для Gather Streams или Repartition Streams; у оператора Distribute Streams только один производитель, таким образом, для него возможен только один поток строк, и нечего объединять для потребителей.



План исполнения запроса

SQL Server показывает все перечисленные выше свойства в плане исполнения запроса (в графическом, текстовом и XML представлении).


Если посмотреть на графическое представление плана исполнения запроса, там сразу видно, какие операторы распараллеливаются (то есть, какие операторы окажутся между стартовым и финишным операторами обмена), это можно узнать по небольшому символу параллелизма, дорисованному к значку оператора:




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



Сравнения списка объектов SQL Server в PowerShell на примере сравнения логинов на двух серверах


В данном примере я покажу как с помощью PowerShell можно сравнивать списки объектов на двух серверах на примере сравнения логинов: поиск одинаковых, поиск разницы. Для этого сначала выполним подключение к серверам. В данном примере первый сервер – локальный. К нему будем подключаться с использованием Windows аутентификации.
Второй сервер – удаленный, к нему будем подключаться с использованием аутентификации SQL Server и запросом на ввод логина и пароля при подключении.

    #1. Загружаем SMO

    [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo ") | Out-Null

    #2. Подключаемся к серверу 1 (Windows аутентификация):
    #Сервер1-локальный
    #Если нужен другой сервер, введите вместо точки IP-адрес \ имя сервера

    $smoserver1 =new-object("Microsoft.SqlServer.Management.Smo.Server") "."

    #3. Подключаемся к Серверу 2 (аутентификация SQL Server):
    #В следующей строке нужно вписать IP-адрес сервера 2 либо имя сервера

    $smoserver2 =new-object("Microsoft.SqlServer.Management.Smo.Server") "IP - адрес \ имя сервера2"

    #Если аутентификация SQL Server, то значение FALSE, если Windows(по умолчанию) – TRUE

    $smoServer2.ConnectionContext.set_LoginSecure($FALSE)

    #Запрос ввода имени пользователя и пароля

    $LoginCredentials = Get-Credential

    #Устанавливаем свойства ConnectionContext

    $smoServer2.ConnectionContext.set_EncryptConnection($FALSE)

    #В конце поста я расскажу зачем в строке ниже убираем слеш из имени пользователя

    $smoServer2.ConnectionContext.set_Login($LoginCredentials.UserName -replace("\"))
    $smoServer2.ConnectionContext.set_SecurePassword($LoginCredentials.Password)

    #4. Сравниваем логины
    #Командлет выводит логины, которые есть на обоих серверах

    compare-object -referenceobject $($smoserver1.logins) -differenceobject $($smoserver2.logins) -includeequal | where-object {$_.SideIndicator -eq "=="} |select-object InputObject

    #Командлет выводит логины, которые есть на сервере 2, но нет на сервере 1

    compare-object -referenceobject $($smoserver1.logins) -differenceobject $($smoserver2.logins) | where-object {$_.SideIndicator -eq "=>"} |select-object InputObject

    #Командлет выводит логины, которые есть на сервере 1, но нет на сервере 2

    compare-object -referenceobject $($smoserver1.logins) -differenceobject $($smoserver2.logins) | where-object {$_.SideIndicator -eq "<="} |select-object InputObject

    #Можно перенаправить вывод в файл

    compare-object -referenceobject $($smoserver1.logins) -differenceobject $($smoserver2.logins) -includeequal | where-object {$_.SideIndicator -eq "=="} |select-object InputObject > C:\Test.txt

По тому же принципу можно сравнивать списки и других объектов сервера, например заданий.

    #Для того, чтобы сравнить список заданий на двух серверах, можно воспользоваться
    #объектом SMOServer.JobServer.Jobs:

    compare-object -referenceobject $($smoserver1.JobServer.Jobs) -differenceobject $($smoserver2. JobServer.Jobs)

Для получения справки по командлету compare-object, в окне powershell введите:

    Get-help Compare-object -detailed

Иерархия объектов SMO приведена в документации по адресу: http://msdn.microsoft.com/ru-ru/library/ms162209.aspx


Теперь о том, почему мы убираем слеш из введенного имени пользователя (...-replace("\")...).

Командлет get-credential ждет ввода в формате Домен\Логин. В нашем примере мы используем его не для входа в домен, а для подключения к SQL Server и домен не вводим, поэтому, несмотря на то, что в окне запроса учетных данных я вводила логин “Inaumova”, свойству UserName присвоилось значение “\Inaumova”. Это показано на рисунках ниже. Поэтому слеш нужно удалить.

Вариант стратегии быстрого и надежного резервного копирования/восстановления VLDB по сети

По материалам технической статьи Майкрософт: A Case Study: Fast and Reliable Backup and Restore of a VLDB over the Network

Автор: Томас Грохсер (Thomas H. Grohser)
При содействии: Линдсей Аллен (Lindsey Allen)
Техническая экспертиза статьи: Sanjay Mishra, Lubor Kollar, Stuart Ozer, Thomas Kejser, Juergen Thomas, James Podgorski, Burzin Patel
Перевод: Александр Гладченко, Ирина Наумова
Редактура перевода: Алексей Халако
Дата издания: июнь 2009г.
Тематика статьи: SQL Server 2008

Резюме: Размер баз данных непрерывно растёт, так же, как и растут требования к надежности и доступности баз. Одновременно с этим как никогда важным становится требование быстрого и надежного восстановления данных. Этот документ посвящён проблемам проектирования устойчивого резервного копирования и решений по восстановлению очень больших баз данных (VLDB). В этой статье на реальном примере демонстрируется как лучше всего использовать функциональность SQL Server 2008 для осуществления резервного копирования и восстановления, которыми обладает SQL Server 2008, а также создание планов резервного копирования и восстановления VLDB по сети.

 

Введение

 

Резервное копирование и восстановление баз данных в SQL Server похожи на многие другие, окружающие нас в реальном мире вещи; Вы сможете не понять их ценность, когда столкнетесь с ними впервые. За эти годы все мы сталкивались с такими ситуациями, когда не оказывалось под рукой нужной резервной копии; следствием этого становилась потеря бизнеса компанией, потеря работы для людей и потери данных. Компании находят оправдания, для того, чтобы не делать резервное копирование и не создавать план восстановления. Часто слышно фразы, подобные этим: “У нас есть кластер, так что нам не нужна резервная копия”, или “Нам не нужен план восстановления, потому что мы делаем резервные копии каждый день”. Что мы поняли за эти годы, так это то, что резервные копии и план восстановления нужны во всех случаях.

 

Краткий обзор Решения

 

 

Соглашение о качестве сервиса

 

Самый важный шаг для создания успешной стратегии резервного копирования, это разработка и утверждение правильного соглашения об уровне сервиса (SLA). Если требования SLA неизвестны, невозможно организовать верную стратегию резервного копирования и восстановления.
SLA должно ответить на следующие вопросы:

     

  • Размер базы данных. Определяется начальный размер и ожидаемый прирост в месяц или в год.
  • Объем рабочей нагрузки. Характеризуется тем, какого размера достигает активная часть журнала транзакций в среднем и во время пиковых нагрузок.
  • Время, необходимое для восстановления. Время восстановления, это максимальное время, необходимое для восстановления из резервной копии после отказа. Не путайте время восстановления и высокую доступность”; высокая доступность характеризуется ситуацией с отказом оборудования, когда восстановление из копии будет единственной возможностью восстановления данных.
  • Сценарий полного отказа. Этот сценарий для самого худшего из возможных сбоев, касающегося задействованного оборудования и программного обеспечения организации в целом. Это может быть отказ всего центра данных, потеря всех данных из-за отказа единственного сервера и т.п.
  •  

Есть много других положений SLA, которые нужно не забыть согласовать, но мы не станем останавливаться в этой статье на абсолютно всех положениях возможного варианта соглашения.
Для рассматриваемого в настоящей статье варианта стратегии, требования в SLA были следующие:

     

  • Размер базы данных был 2 Терабайта, ожидаемый прирост доходил до 4 Терабайт за всё время жизни решения.
  • Объем рабочей нагрузки: очень большой, до 5 Гигабайт в минуту в пиковой загрузке, и приблизительно 0.5 Гигабайта в минуту во время выделенного для резервирования окна.
  • Время восстановления: не более четырёх часов.
  • Сценарий полного отказа: потеря всех данных в результате сбоя в масштабах целого центра данных, а также отказ оборудования центра.
  •  

 

Краткое описание

 

Функции резервного копирования и восстановления предоставляемые SQL Server достаточно просты Основной задачей становится проектирование решений, способных обеспечить исполнение всех требований бизнеса и заданный уровень эффективности даже в случае серьёзного отказа оборудования или программных систем.
Очень большие базы данных (VLDB) привносят свою специфику и дополнительные проблемы в процесс резервного копирования, особенно если эти базы находятся под большой нагрузкой. В целях обеспечения возможности восстановления после аварий вполне резонной мерой является размещение резервных копий баз данных в разных местах. Копирование их по сети привносит дополнительную сложность реализации, по сравнению с резервированием на локальные устройства долговременного хранения. Представьте себе задачу по резервному копированию OLTP базы данных, размером в пару Терабайт; с высокой рабочей нагрузкой; с жёсткими требованиями к производительности обслуживания транзакций и доступности; в данной ситуации абсолютно нереально использовать для резервирования программные интерфейсы (API) виртуальных устройств резервирования (Virtual Backup Device Interface – VDI), позволяющие задействовать имеющиеся в современных системах хранения (SAN) возможности резервирования, пригодные для использования с SQL Server. Кроме того, если задача осложняется ещё и тем, что соглашение о качестве сервиса требует полного восстановления системы менее чем через четыре часа (включая применение всех файлов копий журнала транзакций). Эта техническая статья как раз и посвящена описанию возможного варианта решения именно такой задачи, реализуемой с помощью средств SQL Server 2008.
Полное решение резервного копирования должно включать в себя как полную резервную копию, так и возможно, разностные резервные копии и множество резервных копий журнала транзакций. В описываемой тут стратегии, разностные резервные копии приводили бы к фактическому увеличению суммарной продолжительности процедуры восстановления баз данных, так что они в предлагаемом решении использоваться не будут. Резервные копии журнала транзакций в решении присутствовать будут, и выполняться они будут с частотой – один раз в минуту. Внедрение и настройка решений, когда резервные копии журнала транзакций доставляются и восстанавливаются на другом сервере, для баз данных большого размера само по себе достаточно сложное дело, так что описание подобных решений будет сделано в других статьях. Эта же статья сосредоточена на описании высокопроизводительных и хорошо масштабируемых методов создания полных резервных копий.

 

Как использовать данный документ

 

Вам предлагается краткий обзор того, как надежно и быстро создавать по сети полные резервные копии баз данных. Помимо формализации требований SLA, в статье показано, как задача была реализована и какие варианты решения и параметры были избраны для возможных настроек команд резервного копирования и восстановления. В тексте также встречаются разнообразные советы и рекомендации, которые могут вам помочь при проектировании подобных решений в промышленной среде.
Эта статья нацелена на описание эффективной стратегии резервного копирования и восстановления, и полагается на те возможности, которыми располагает SQL Server 2008 (большинство возможностей также доступны в SQL Server 2005, но все примеры сценариев используют синтаксис и возможности SQL Server 2008). Те же самые принципы подходят и для разностных резервных копий и копий журнала транзакций, но углублённое описание этих стратегий выходит за рамки настоящей статьи.
Эта статья не ставит целью описать пригодный на все случаи жизни процесс резервного копирования или план восстановления, она ограничивается описанием того, как реализовать надежное резервное копирование, и подобные реализации применимы для промышленных стратегий защиты от потери данных. Поскольку частота копирования данных зависит от бизнес-требований, вопрос частоты создания резервных копий баз данных также выходит за рамки этой статьи.

 

Реализация надежного резервирования по сети

 

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

 

Когда резервную копию можно считать правильной?

 

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

     

  • она должна находиться в другом месте, например, в другом центре данных.
  • она должна успешно восстанавливаться.
  • восстановленная база данных не должна показывать ошибки при запуске DBCC CHECKDB.
  •  

В итоге, полный цикл резервирования состоит из следующих шагов:

  1. Резервное копирование базы данных.
  2. Копирование файлов на хранение в удаленную область. В качестве хранилища может использоваться файловый сервер, ленточное устройство или их комбинация (этот шаг может быть объединен с первым, когда резервное копирование осуществляется непосредственно на устройство в сети).
  3. Восстановление базы данных из удаленной области.
  4. Выполнение проверки восстановленной базы средствами DBCC CHECKDB.
  5. Выполнение проверки корректности самих данных, чтобы удостовериться в их логической правильности с точки зрения приложения и пользователей. Этот шаг является дополнительным, но мы рекомендуем не исключать его из цикла. Основная часть проверки с прикладной точки зрения затрагивает трансформацию (ETL) данных из восстановленной базы данных в информационное хранилище, затем выполняется заранее продуманный набор отчётов, позволяющих убедиться в целостности данных.

 

    Обратите внимание: Не все компании имеют удаленный центр данных и, если невозможно выполнить восстановление в другом месте, храните резервные копии в разных местах, не там, где исходные базы данных. Никогда не размещайте резервные копии в той же самой стойке, или на том же самом сервере, или на том же диске где размещены исходные данные.

     

    Основные опции встроенного механизма резервирования и восстановления

     

    Встроенные средства резервного копирования и команды восстановления в SQL Server 2008 предоставляют следующие возможности:

       

    • Резервное копирование на локальный диск и последующее копирование файлов по сети.
    • Резервное копирование непосредственно на ресурс для хранения резервных копий в сети.
    •  

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

     

    Что может пойти не так?

     

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

       

    • Некорректная запись бит данных в файл резервной копии во время исполнения команды резервного копирования.
    • Обслуживающий базу данных сервер выходит из строя во время операции резервного копирования.
    • Хранилище резервных копий выходит из строя во время операции резервного копирования.
    • Сбои в работе хранилища резервных копий после завершения резервного копирования.
    •  

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

     

    Выполнение надежного резервного копирования по сети

     

    Допустим, имеется база данных с именем MyVLDB, которую нужно сохранить. База данных MyVLDB находится на сервере с именем SQL01. Файл-сервер, на котором мы хотим разместить резервную копию, называется BAK01. Полностью квалифицированное имя файлового ресурса \\backup. На рисунке 1 проиллюстрирована эта конфигурация.


    Рисунок 1: Резервное копирование по сети

    Следующая команда выполняет резервное копирование базы данных на файловый сервер.

      BACKUP DATABASE MyVLDB
      TO DISK = '\\BAK01\backup\MyVLDB.bak'
      WITH INIT

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

     

    Защита от некорректной записи в файл резервной копии

     

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

      BACKUP DATABASE MyVLDB
      TO DISK = '\\BAK01\backup\MyVLDB.bak'
      WITH CHECKSUM, INIT;

    Опция WITH CHECKSUM помещает контрольную сумму на каждую страницу файла резервной копии, что позволяет обнаруживать повреждения страниц файла резервной копии во время её восстановления. Резервное копирование с опцией CHECKSUM будет потреблять всего на 2%-3% больше ресурсов, чем предыдущий вариант резервирования, но это с лихвой окупается повышением надежности резервирования.

     

    Защита от отказа сервера баз данных во время резервирования

     

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

      BACKUP DATABASE MyVLDB
      TO DISK = '\\BAK01\backup\MyVLDB<BackupSequenceNumber>.bak'
      WITH CHECKSUM, INIT;

    Другой способ заключается в добавлении к имени файла даты и времени резервного копирования, и этот способ применим, если копирование достаточно делать раз в час или один раз в день. Следующий пример создает порядковый номер резервной копии, используя для этого функцию вычисления универсального времени (UTC):

      -- использование GETUTCDATE() в качестве порядкового номера копии
      DECLARE @FileName AS nvarchar(4000) = '\\BAK01\backup\MyVLDB' +
              CONVERT(nvarchar(32), GETUTCDATE(), 112 )+ '.bak';
      BACKUP DATABASE MyVLDB
      TO DISK = @FileName
      WITH CHECKSUM, INIT;

    Альтернативный пример представлен ниже, в нём порядковый номер резервной копии создаётся путём приращения единицы к текущему порядковому номеру каждый раз, когда выполняется резервное копирование:

      -- для нумерации копий используется возрастающая последовательность чисел
      -- представленный ниже сценарий должен существовать отдельно для каждой БД,
      -- каждая нумерация должна иметь свою создаваемую специально таблицу
      USE MyVLDB;
      GO
      CREATE SCHEMA DBBackup;
      GO
      CREATE TABLE DBBackup.Sequence(
             Number int NOT NULL,
             ID int NOT NULL,
             CONSTRAINT PK_Seq PRIMARY KEY CLUSTERED (ID ASC)
      );
      ALTER TABLE DBBackup.Sequence WITH CHECK ADD CONSTRAINT CK_Seq CHECK (ID=1);
      ALTER TABLE DBBackup.Sequence CHECK CONSTRAINT CK_Seq;
      INSERT INTO DBBackup.Sequence (Number) VALUES (1,1);

      -- это соответствующая команда резервного копирования
      DECLARE @SeqNumber AS int;
      UPDATE MyVLDB.DBBackup.Sequence SET @SeqNumber= Number= Number+ 1 WHERE ID=1;
      DECLARE @FileName AS nvarchar(4000) = '\\BAK01\backup\MyVLDB'
              RIGHT('0000000' + CAST(@SeqNumber AS NVARCHAR(8)), 8) + '.bak';
      BACKUP DATABASE MyVLDB
      TO DISK = @FileName
      WITH CHECKSUM, INIT;

    Хотя этот метод выглядит более сложным, он более гибок. Следующие ниже примеры будут основываться на этом методе нумерации копий.

     

    Защита от отказа сервера хранения копий

     

    Файловые серверы для хранения резервных копий также могут отказать и от этого необходимо предусмотреть защиту. Способ предотвращения подобного отказа состоит в том, чтобы использовать дополнительный сервер (в нашем примере это BAK02). Размещение этого сервера ещё в одном центре данных (отличном от двух рассмотренных выше) обеспечивает ещё большую защиту в случае отказа.
    На следующем рисунке показана последовательность резервного копирования, которая использует запасной резервный сервер и применяет другие методы, которые были рассмотрены ранее.


    Рисунок 2: Защита от отказа сервера резервных копий

    Резервная копия делается поочерёдно на двух серверах, и после двух полных циклов резервного копирования становится устойчивой к отказам, поскольку будут существовать две полные резервные копии на двух разных серверах в разных центрах данных.
    Если запасного файлового сервера нет, желательно попробовать разделить логические диски (LUN) так, чтобы при хранении нескольких клонов резервной копии, частичный отказ дисковой подсистемы затронул бы только один набор файлов резервных копий.

     

    Проверка восстанавливаемости резервной копии

     

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

     

    Восстановление в базу данных Test Backup

     

    Для того чтобы восстановить из копии тестовую базу данных, нужно иметь ещё один экземпляр SQL Server, установленный на резервном сервере, и ещё один логический диск – LUN, доступный серверу резервных копий. На втором сервере во время восстановления будут временно размещены файлы базы данных (диск должен быть достаточно большим, чтобы поместились файлы базы данных, размер которых может быть максимально возможным в обозримой перспективе). Для этих целей дисковый массив RAID0 является приемлемым компромиссом между производительностью, уровнем затрат и доступностью. Избыточность дисковых массивов тут не нужна, потому что база данных будет существовать временно. Уровень RAID 1/0 также допустим, если вы можете это себе позволить.
    Ниже показана схема каталогов файловой системы сервера хранения резервных копий. Схема предусматривает место для размещения файлов восстанавливаемой базы данных.


    Рисунок 3: Файловая система сервера хранения резервных копий.

    Для того чтобы получить из файла резервной копии MyVLDB00000001.bak информацию о схеме размещения базы данных по дискам, нужно выполнить следующую команду:

      RESTORE FILELISTONLY FROM DISK = 'c:\backup\MyVLDB00000001.bak';

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

      RESTORE DATABASE MyVLDB
      FROM DISK = 'C:\backup\MyVLDB00000001.bak'
      WITH
      MOVE 'MyVLDB' TO 'C:\restore\MyVLDB.mdf',
      MOVE 'MyVLDB_log' TO 'C:\restore\MyVLDB.ldf',
      CHECKSUM;

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

     

    Выполнение DBCC CHECKDB

     

    Чтобы проверять целостность восстановленной базы данных, выполните следующую команду:

      DBCC CHECKDB(MyVLDB) WITH NO_INFOMSGS, EXTENDED_LOGICAL_CHECKS, DATA_PURITY

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

     

    Автоматизация описанного процесса

     

    Ниже представлен сценарий, который автоматически извлекает имена файлов из первого файла резервной копии и передаёт их на вход команды восстановления базы, выполняя затем инструкцию DBCC CHECKDB.

      DECLARE @dbName AS sysname = 'MyVLDB';
      DECLARE @BackupFileName AS nvarchar(4000) = 'C:\backup\MyVLDB00000001.bak';
      DECLARE @DBFiles AS TABLE
      (
             LogicalName nvarchar(128),
             PhysicalName nvarchar(260),
             Type char(1),
             FileGroupName nvarchar(128),
             Size numeric(20,0),
             MaxSize numeric(20,0),
             FileID bigint ,
             CreateLSN numeric(25,0),
             DropLSN numeric(25,0) NULL,
             UniqueID uniqueidentifier,
             ReadOnlyLSN numeric(25,0) NULL,
             ReadWriteLSN numeric(25,0) NULL,
             BackupSizeInBytes bigint,
             SourceBlockSize int,
             FileGroupID int,
             LogGroupGUID uniqueidentifier NULL,
             DifferentialBaseLSN numeric(25,0) NULL,
             DifferentialBaseGUID uniqueidentifier,
             IsReadOnly bit,
             IsPresent bit,
             TDEThumbprint varbinary(32) NULL
      );
      INSERT INTO @DBFiles
             EXEC sp_executeSQL N'RESTORE FILELISTONLY FROM DISK = @FileName',
                    N'@FileName NVARCHAR(4000)', @FileName = @BackupFileName;
      -- Ищем выполненную последней, завершённую резервную копию
      DECLARE @lastRestoreID AS bigint = ISNULL(
             (SELECT TOP(1) restore_history_id FROM msdb.dbo.restorehistory
                    WHERE restore_type = 'D' and destination_Database_Name = @dbName
                    ORDER BY restore_history_id DESC), 0);

      -- Создаём сценарий проверки восстанавливаемости
      DECLARE @sqlcmd AS nvarchar(4000) =
      'RESTORE DATABASE @RestoreName FROM DISK = @FileName WITH ';
      DECLARE @IsFirst AS tinyint = 1;
      WHILE ((SELECT COUNT(*) FROM @DBFiles) > 0)
      BEGIN
             DECLARE @LN AS sysname = (SELECT TOP(1) LogicalName FROM @DBFiles ORDER BY LogicalName);
             SET @SQLcmd+='MOVE '''+@LN+''' TO ''C:\Restore\'+@dbname+@LN+'.dbf''';
             DELETE @DBFiles WHERE LogicalName = @LN;
             SET @SQLcmd += ', ';
      END
      SET @SQLcmd += ' CHECKSUM';
      DECLARE @RC AS int;
      EXEC @RC = sp_executeSQL @SQLCmd,
      N'@RestoreName sysname, @FileName nvarchar(4000)',
             @RestoreName = @dbName, @FileName = @BackupFileName;
      IF (@RC <> 0 OR NOT EXISTS(SELECT TOP(1) *
                    FROM msdb.dbo.restorehistory
                    WHERE restore_type = 'D'
                           AND destination_Database_Name = @dbName
                           AND restore_history_id > @lastRestoreID))
             RAISERROR ('Database restore NOT sucessfull', 16, 0);
      DBCC CHECKDB(@dbName) WITH NO_INFOMSGS, EXTENDED_LOGICAL_CHECKS, DATA_PURITY
      IF @@ERROR <> 0 RAISERROR ('Database restore NOT sucessfull', 16, 0);

     

    Высокая производительность резервирования по сети

     

    Копирование двухтерабайтной базы данных на локальные жесткие диски и затем её восстановление удовлетворяет временным требованиям на восстановление нашей системы SQL Server. Однако, этот простой процесс не может обеспечить адекватную защиту от сценариев полного отказа ,формализованных в соглашении SLA. С другой стороны, копирование базы данных по сети в удаленное хранилище защиту от полного отказа обеспечить может. Проблему может создать ограничение, накладываемое полосой пропускания сети, которое обычно не превышает обычно 1 Гигабита в секунду (Gbps). Когда мы изучали данную ситуацию, основным критерием для сравнения производительности было копирование данных по сети 1-Gbps на расстояние в 10 миль в другой центр данных. Этот процесс занимал более 24-х часов, что было совершенно неприемлемо. Необходимо было найти такое решение, которое давало бы возможность выполнять резервное копирование в рамках того окна времени восстановления, которое было указано в SLA.
    Всесторонне переосмыслив все грани проблемы резервирования по сети и проведя множество испытаний, мы смогли сократить резервное копирование базы в 2Тб до 36 минут. Решение, которое мы назвали “многопоточным резервированием по сети” (в оригинале: “multistreamed backups over the network”), использовало восемь сетевых подключений, по 1-Gbps каждое. Увеличенный до гигантского размер фрейма был указан в настройках каждой сетевой платы, и каждая сетевая плата была соединена с задублированным по второму уровню коммутатором линией 10GE (10Gbit Ethernet), которая протянулась до второго сайта. Само резервное копирование заняло 2 часа 25 минут. Появившееся в SQL Server 2008 сжатие резервных копий позволило ещё сократить это время до 36 минут. База данных состояла из 32 файлов данных и одного файла журнала транзакций, которые занимали приблизительно 2,5 Терабайта на 9 логических дисках (файлы данных размещались на дисковом хранилище корпоративного класса – SAN, а журнал транзакций на локальных дисках – DAS). В таблице ниже показана продолжительность резервного копирования обычным способом и два наиболее быстрых подхода к резервированию по сети. Подробное описание каждой из реализаций приведено ниже.

    Попытка Сеть Продолжительность
    Обычным способом Одна 1Gbps сетевая плата, конфигурация по умолчанию >24 часов
    Многопоточное резервирование по сети 8x1Gbps сетевых плат, гигантский фрейм 2 часа 25 минут
    Многопоточное резервирование по сети со сжатием 8x1Gbps сетевых плат, гигантский фрейм 36 минут

    Таблица 1: Продолжительность резервирования 2Tб на сервер в 10 милях по сети

     

    Сокращение времени резервного копирования

     

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


    Рисунок 4: Типичное размещение файлов резервной копии базы по дискам

     

    Параллельное использование нескольких дисков и файлов

     

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


    Рисунок 5: Добавление логических дисков

    Ниже показана команда, которая выполняет резервное копирование в два файла.

      BACKUP DATABASE MyVLDB
      TO DISK = '\\BAK01\backup\MyVLDB00000001_1.bak',
         DISK = '\\BAK01\backup\MyVLDB00000001_2.bak'
      WITH CHECKSUM, INIT;

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

     

    Использование нескольких сетевых плат

     

    При резервном копировании по сети узким местом часто становится сеть. Увеличение пропускной способности сети больше чем 1 Gbps недешево, однако увеличение числа гигабитных сетевых плат в сервере относительно недорогая опция.
    Если для резервного копирования добавить две сетевые платы на сервере базы данных и две на файловом сервере, где резервная копия будет храниться, конфигурация примет вид, показанный на рисунке 6.


    Рисунок 6: Добавление сетевых плат

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

    Сетевая плата Сервер SQL01 Сервер BAK01
    Access 192.168.1.1 МАСКА 255.255.255.0 192.168.1.2 МАСКА 255.255.255.0
    Backup 01 192.168.2.1 МАСКА 255.255.255.0 192.168.2.2 МАСКА 255.255.255.0
    Backup 02 192.168.3.1 МАСКА 255.255.255.0 192.168.3.2 МАСКА 255.255.255.0

    Таблица 2: Подсети

    Каждая сетевая плата находится в своей подсети (192.168.1.0/24, 192.168.3. 0/24). Теперь можно внести небольшие изменения в команду резервного копирования, указав там IP – адреса вместо имён серверов. Таким способом становится легко управлять тем, какая подсеть, а, следовательно, и какая сетевая плата будет использоваться для транспортировки данных. Тот факт, что все логические подсети будут находиться на одном и том же втором физическом уровне сети, не будет иметь никакого отрицательного влияния на это решение.

      BACKUP DATABASE MyVLDB
      TO DISK = '\\192.168.2.2\backup\MyVLDB00000001_1.bak',
         DISK = '\\192.168.3.2\backup\MyVLDB00000001_2.bak'
      WITH CHECKSUM, INIT;

    В случае восстановления, это работает по той же схеме.

      RESTORE DATABASE MyVLDB
      FROM DISK = '\\192.168.2.2\backup\MyVLDB00000001_1.bak',
           DISK = '\\192.168.3.2\backup\MyVLDB00000001_2.bak'
      WITH CHECKSUM, NORECOVERY;

    Точно также всё будет работать и с большим числом сетевых плат. В описываемых в статье экспериментах проверялась параллельная работа 16 сетевых плат.
    Увеличение производительности будет наблюдаться и от передачи по сети нескольких файлов одновременно с помощью одной сетевой платы.

      BACKUP DATABASE MyVLDB
      TO DISK = '\\192.168.2.2\backup\MyVLDB00000001_1.bak',
         DISK = '\\192.168.3.2\backup\MyVLDB00000001_2.bak',
         DISK = '\\192.168.2.2\backup\MyVLDB00000001_3.bak',
         DISK = '\\192.168.3.2\backup\MyVLDB00000001_4.bak'
      WITH CHECKSUM, INIT;

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

     

    Рекомендации по общему числу используемых файлов

     

    На основании результатов множества экспериментов, были выработаны рекомендации по оптимизации резервного копирования. Подспудно было также выяснено, что резервное копирование работало лучше в тех случаях, когда все ресурсы загружались равномерно. Для получения оптимальных результатов, все представленные ниже равенства должны выдерживаться. Уравнения перечислены в порядке их значимости, с представлением рекомендованных значений для n, которые выделены жирным шрифтом.

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

    Процес-
    сорные
    ядра
    Логические
    диски
    для
    файлов
    данных
    Сетевые
    платы
    для
    резервного
    копирования
    Файлы
    данных
    Файлы
    резервных
    копий
    2 1 1 2 2
    4 2 2 2 4
    4 4 2 4 4
    8 2 2 2 8
    8 4 2 4 8
    16 2 2 8 8
    16 4 4 8 8
    16 4 4 16 16
    16 8 4 16 16
    32 8 4 16 16
    32 8 8 16 32
    32 16 8 16 32
    64 16 8 32 32
    64 32 16 32 64

    Таблица 3: Сбалансированное распределение файлов по дискам

     

    Дополнительные способы оптимизации и рекомендации

     

     

    Разделение сетей для резервного копирования и общего доступа

     

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


    Рисунок 7: Эффективное использование аппаратных средств сетевого коммутатора

    Конфигурация на Рисунке 7 использует аппаратные средства сетевого коммутатора существенно эффективнее, чем на рисунке 8.


    Рисунок 8: Менее эффективное использование аппаратных средств сетевого коммутатора

     

    Большие фреймы

     

    Максимальный размер пакета сети Ethernet в нормальных условиях составляет 15000 байт (равен размеру фрейма). Это означает то, что для передачи по сети 1 Мегабайта, его придётся разбить на 700 пакетов, которые будут переданы один за другим.
    Сегодня можно приобрести такие сетевые платы и коммутаторы, которые поддерживают пакеты Ethernet с большими размерами фрейма. Для таких фреймов сетевых плат и коммутаторов даже существует специальное название – “jumbo frames“.
    Чем больше размер фрейма, тем быстрее передача данных, потому что для обмена между серверами потребуется меньше итераций.
    Наиболее распространёнными размерами больших фреймов являются величины около 4088 и 9016 Байт (включая заголовки Ethernet и большого фрейма). Например, если размер фрейма будет 9016 Байт, тогда для передачи 1 Мегабайта понадобится всего 117 пакетов.
    Эмпирические исследования показали, что при увеличении размера фрейма до 9016 Байт, производительность сети практически удваивается.

     

    BLOCKSIZE

     

    Параметр, который задаёт используемый командой BACKUP размер блока, должен соответствовать размеру блока записи устройств долговременного хранения. Когда запись осуществляется на отдельный диск, будет работать достаточно хорошо даже используемое по умолчанию для размера блока значение равное 512. Если же запись направлена на RAID – массив или на SAN, стоит убедиться в том, что используемое по умолчанию значение не окажется меньше, чем, например, 65536.
    При резервном копировании по сети нужно подобрать такое значение, которое бы позволило заполнять сетевые пакеты наиболее плотно. Имейте также в виду, что разбиение данных на пакеты работает в обоих направлениях. Выбор в качестве размера блока 512 Байт приведет к тому, что в сетевой пакет будет помещаться два блока (принимая во внимание то, что размер фрейма Ethernet равен 1500 Байт). Т.о. для передачи одной страницы базы данных понадобится 8 сетевых пакетов. С другой стороны, запись блоками по 4096 Байт будет помещаться в 3 сетевых пакета, а для передачи одной страницы базы данных понадобится 6 сетевых пакетов.
    Можно привести ещё дополнительные примеры, полученные в результате проводимых при написании настоящей статьи тестов; при использовании больших фреймов размером 9016 Байт наилучшие результаты получались при размере блока 8192 Байт, а при использовании больших фреймов размером 4088 Байт, наилучшие результаты получались при размере блока 65536 Байт.

     

    BUFFERCOUNT и MAXTRANSFERSIZE

     

    Из параметров команды BACKUP можно выделить такие, которые также очень сильно влияют на производительность резервного копирования, это параметры BUFFERCOUNT и MAXTRANSFERSIZE. К сожалению, даже недели тестов не смогли помочь составить правило подбора оптимальных значений для этих параметров, поэтому Вам также необходимо будет выяснять оптимальные значения тестированием в Вашей среде. В качестве совета. для значения параметра MAXTRANSFERSIZE если у Вас система x64 или IA64 с достаточным объёмом оперативной памяти, можно начать тестирование со значения максимального размера буфера 4 Мб (4194304). Для получения более подробной информации о параметре BUFFERCOUNT и о других оптимизирующих параметрах, обратитесь к рекомендациям по настройке производительности сжатия резервных копий в технической статье: Tuning the Performance of Backup Compression in SQL Server 2008
    В некоторых случаях, при проведении тестов для этой статьи, лучшие результаты получались при существенно меньших значениях параметров, но выбор значений был непредсказуем. Для промышленного применения стоит выполнить всестороннее тестирование разных вариантов, и убедиться, что лучшие результаты хорошо воспроизводятся. Если же нет возможности провести такую работу – лучше сохранить параметры по умолчанию.

     

    Сжатие резервной копии

     

    Сжатие резервных копий (новая функция, появившаяся в SQL Server 2008) предоставляет возможность увеличить производительность резервирования и в то же время существенно сократить потребляемое копией дисковое пространство, которое выделено для хранения резервных копий. Для включения сжатия резервной копии, в команде BACKUP нужно добавить опцию WITH COMPRESSION.
    В представленном ниже примере запроса показано, как включить сжатие резервной копии.

      BACKUP DATABASE MyVLDB
      TO DISK = '\\BAK01\backup\MyVLDB.bak'
      WITH CHECKSUM, INIT, COMPRESSION;

    Степень сжатия в действительности зависит от данных, которые хранятся в базе. Для большинства баз данных (цифровая информация, денежно-кредитные операции, дата и время, простой текст), коэффициент сжатия будет находиться между 1:4 и 1:6. Для баз данных содержащих некоторые другие типы данных, например, картинки, которые уже в сжатом формате, можно ожидать результаты похуже. Для получения более подробной информации об использовании сжатия с разными типами данных, смотрите статью в SQL Server Books Online: Сжатие резервных копий.
    В проводимых для этой статьи тестах, наблюдалось сокращение времени резервного копирования с 125 до 36 минут, при сжатии файла на 20 процентов от первоначального размера.
    У сжатия данных есть один недостаток- повышение утилизации процессорных ресурсов.
    SQL Server производитт сжатие в одном потоке, который пишет данные в файл резервной копии, так что число файлов резервных копий определяется числом процессорных ядер, которые будут выполнять сжатие параллельно. Чтобы ограничить использование процессоров для резервного копирования, можно использовать Регулятор Ресурсов (Resource Governor), с помощью которого можно отдавать другим подключениям больше ресурсов.
    Если используется прозрачное шифрование базы данных (TDE), не следует пытаться использовать для шифруемой базы ещё и сжатие, потому что зашифрованные данные сжиматься достаточно плохо. Если указание опции сжатия при формировании каждой команды резервного копирования неудобно, можно с помощью системной хранимой процедуры sp_configure установить автоматическое сжатие при создании всех резервных копий на сервере:

      sp_configure 'backup compression default', 1
      reconfigure

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

     

    Рекомендации по аппаратным средствам файловых серверов, предназначенных для хранения копий

     

     

    Дисковые устройства

     

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

     

    Настройка RAID-контроллера

     

    Для создания массивов логических дисков, на которых решено хранить файлы резервных копий, стоит выбрать большой размер сегмента (64 Кб, 128 Кб, 256 Кб и выше). Также, стоит установить полное кэширование записи, а кэширование чтения можно полностью отключить. Можно ещё активизировать кэш записи отдельных шпинделей, так как если во время резервного копирования произойдёт сбой по питанию, резервная копия так и так станет непригодной, и в таком случае не имеет значения, были ли потеряны какие-либо байты в кэше записи или нет.
    Для тех логических дисков, которые будут задействованы в пробном восстановлении базы из копии, размер сегмента устанавливается в 64 Кб, применяется политика 100-процентного кэширования записи, а кэширование чтения выставляется на 0 процентов.
    Выбор уровня RAID (1, 10, 5, 6 …) зависит от возможностей используемого RAID-контроллера или используемой системы хранилища. Поскольку нагрузка на файловом сервере при резервном копировании/восстановлении является последовательной записью и чтением данных, контроллер будет кэшировать данные, пока он не закончит запись всего страйпа целиком, в этом случае можно использовать любой уровень RAID. Если контроллер ведёт себя по-другому, и производительность является критическим параметром, массивы RAID1 или RAID10 будут единственным возможным вариантом.

     

    Настройка HBA

     

    Если в качестве дисков для файлов резервных копий используется дисковая подсистема типа SAN, максимальную глубину очереди для адаптеров, которые используются в подключении SAN, нужно увеличить настолько, насколько это возможно. Значение по умолчанию составляет 32, но резервное копирование и восстановление будут работать намного лучше при значениях близких к 256. Более подробную информацию можно найти в статье Настройка Windows Server 2008/2003 x64 для обслуживания SQL Server 2008 , в разделе “NumberOfRequests и MaximumSGList”.

     

    Сетевые платы

     

    Следует очень разборчиво подходить к выбору сетевых плат, которые будут использоваться на серверах. Число портов ещё не гарантирует адекватную производительность ввода-вывода для всех этих портов в одно и то же время. Бывает так, что два четырехпортовых адаптера могут оказаться более производительными, чем один адаптер с четырьмя портами. Количество процессорного времени, используемого драйвером сетевого интерфейса, также очень важно. Бывают такие сетевые платы, которые используют до 50 процентов ресурсов одного процессора, и в то же время есть другие, которые используют всего 3 – 5 процентов.
    Если для резервного копирования используется несколько сетевых плат, очень важно, чтобы они использовали разные процессоры, т.е. чтобы их прерывания были привязаны к разным процессорным ядрам.

     

    Системы на основе NUMA

     

    Если сервер использует архитектуру с неоднородным доступом к памяти (NUMA), необходимо убедится в том, что все адаптеры ввода-вывода (например, NIC, RAID и HBA) распределены между всеми NUMA – узлами системы.

     

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

     

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

    Если ожидается постоянный рост объёма данных во времени, размер файла полной резервной копии по истечении заданного периода времени может быть определен по следующей формуле:

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

    К полученному по формуле значению всегда стоит добавлять не меньше 10 процентов, в качестве буфера безопасности.
    Далее, нужно определить максимальную продолжительность параллельного, последовательного чтения и записи используемых дисковых подсистем. Для того чтобы измерить эти значения при тестировании резервного копирования и восстановления, можно использовать системную утилиту Performance Monitor (известную ещё в некоторых версиях операционной системы Windows как System Monitor).
    В результате, должно получиться 5 значений. Если имеется несколько логических дисков и сетевых плат, эти значения могут отличаться, и всегда нужно использовать самые худшие результаты вычислений.

       

    1. Производительность сетевого адаптера (МБ/сек);
    2. Производительность логического диска хранилища копий при последовательном чтении (МБ/сек);
    3. Производительность логического диска хранилища копий при последовательной записи (МБ/сек);
    4. Производительность логического диска файла базы данных при последовательном чтении (МБ/сек);
    5. Производительность логического диска файла базы данных при последовательной записи (МБ/сек).
    6.  

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

    Коэффициент сжатия резервной копии определяется так:

    Если сжатие не используется, это приведет к тому, что коэффициент сжатия станет таким: CompressionFactor = 1.
    Производительность резервного копирования будет ограничена следующим значением:

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

    Вычисления для расчета времени восстановления будут сложнее. Сначала нужно узнать, поддерживает ли используемая система мгновенную инициализацию файлов. Эта возможность позволяет SQL Server создавать файлы данных на томах NTFS без обнуления занимаемого файлами места во время создания или расширения файла. Поскольку у этого механизма существуют связанные с безопасностью риски, такой возможностью можно воспользоваться, только если учетной записи, под которой запущена служба SQL Server предоставить в локальных политиках право “Perform Volume Maintenance”. Если учетная запись пользователя входит в группу локальных администраторов, это право ей будет предоставлено по умолчанию (Примечание: время инициализации файла журнала транзакций может ограничивать производительность, так как занимаемое этим файлом место не может не заполняться нулями).
    Если разрешена мгновенная инициализация файлов, а значение FileInitFactor равно коэффициенту сжатия резервной копии, мы получим следующую формулу:

    И точно так же, как в случае с расчетом времени резервного копирования, оценить время на восстановление из копии можно так:

    В случае если RestoreTime или BackupTime выше заданных SLA значений, можно воспользоваться изложенными ранее рекомендациями по уменьшению этих значений. Распараллеливание операций обычно ускоряет процесс больше чем попытка ускорить работу одного из компонент во всей цепочке. В сценариях с очень высокой производительностью стоит задуматься о применении обоих подходов.
    Для расчета времени выполнения полного цикла резервирования (включая проверку), можно использовать следующую формулу:

    Значение BackupTime тут используется такое, как было описано ранее, а две другие переменные вычисляются так:

    Обратите внимание: на исполнение команды CHECKBD может потребоваться больше времени, чем время, которое необходимо для чтения с диска. Оно зависит от сложности схемы базы данных, и оно точно не будет меньше времени чтения.

     

    Заключение

     

    Microsoft и, непосредственно команда SQL Server постоянно совершенствуют технологии обеспечения живучести систем, которые призваны помочь клиентам и партнерам решать задачи резервного копирования и восстановления в масштабах предприятия. SQL Server 2008 обеспечен для этих задач всем необходимым функционалом, на который могут положиться специалисты и который помогает успешно справляться с присущими сегодняшнему дню требованиями по управлению и защите данных. За счёт усовершенствований в ключевых областях, SQL Server 2008 стал надёжной платформой для обслуживания очень больших баз данных.
    Эта техническая статья предоставляет только краткий обзор возможностей резервного копирования и восстановления, а также некоторых функциональных возможностей SQL Server 2008. Для получения более подробной информации, посетите посвящённый SQL Server сайт:

    Более подробную информацию можно получить по следующим ссылкам:

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

       

    • Является ли оценка высокой из-за хороших примеров, хороших снимков с экрана, понятного изложения или по другим причинам?
    • Является ли оценка низкой из-за неудачных примеров, некачественных снимков с экрана или плохого изложения материала?
    •  

    Обратная связь поможет нам сделать выпускаемые технические статьи лучше. Пишите нам по адресу: sqlfback@microsoft.com

     

     

    Приложение: Используемые аппаратные средства

     

    Сервер баз данных:

       

    • HP Superdome с 16 двуядерными процессорами, 1.6 ГГц, 24 Мб кэш, 256 Гб ОЗУ;
    • 8 двухпортовых HBA для подключения к SAN файлов данных БД;
    • 8 RAID контроллеров SAS для журнала транзакций;
    • 32 сетевые платы;
    • Логические диски SAN были каждый по 512Гб, составленные из 256 шпинделей 15K RPM, собранные в RAID10.
    •  

    Файловые серверы копий:

       

    • HP Integrity rx 7640 Servers с 8 двуядерными процессорами, 1.6 ГГц, 8 Мб кэш, 32Гб ОЗУ;
    • 4 RAID контроллера SAS (каждый с кэшем 512 Мб);
    • 96 SATA дисков 7,200 RPM, собранные в 32 логических диска RAID1;
    • Остальные диски использовались для создания 4 логических дисков RAID0, использовавшихся для восстановления базы данных;
    • 8 двухпортовых сетевых плат (8 портов использовались для файлового ресурса резервных копий).
    •  

    Сетевое оборудование:

       

    • Коммутатор Cisco C3750E с размером фрейма 4 088 байт
    •  

    Параметры настройки резервного копирования:

       

    • BLOCKSIZE: 65536
    • BUFFERCOUNT: 256
    • MAXTRANSFER: 4 Мб
    • Число файлов резервной копии: 32 (по 4 на сетевую плату)
    •  


    Рисунок 9: Сайзинг дисков и распределение файлов на сервере баз данных и файл-сервере резервных копий.

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


    Рисунок 10: Конфигурация сети сервера баз данных для резервного копирования.


    Рисунок 11: Конфигурация сети и дисковой подсистемы файлового сервера резервных копий

    Данный перевод опубликован на сайте SQLCAT: http://sqlcat.com/whitepapers_russian/archive/2009/11/25/pagelatch-insert.aspx

    Индексные объединения

    По материалам статьи Craig Freedman: Index Union
    Перевод Ирины Наумовой

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

    Начнем:

    create table T (a int, b int, c int, x char(200))
    create unique clustered index Ta on T(a)
    create index Tb on T(b)
    create index Tc on T(c)

    insert T values (1, 1, 1, 1)
    insert T values (2, 2, 2, 2)
    insert T values (3, 3, 3, 3)

    select a from T where b = 1 or b = 3

      |--Index Seek(OBJECT:([ Т ].[ Тb ]), SEEK:([ Т ]. [ b ]=(1) OR [ Т ]. [ b ]=(3)) ORDERED FORWARD)

    У нас имеется индекс на поле “b”, и, как и ожидалось, оптимизатор выбирает поиск по индексу. Поскольку мы имеем два предиката для поля “b”, мы получим поиск с двумя предикатами. Вначале выполнится предикат “b=1″, а зетем “b=3″. Обратите внимание, что поскольку мы выводим столбец “a”, а он является кластеризованным (и таким образом покрывает все некластеризованные индексы), не возникает необходимости в операции BOOKMARK LOOKUP (поиск закладок). Пока нет никаких неожиданностей.
    Обратите внимание, что мы могли записать этот запрос в ином виде:

    select a from T where b = 1
    union all
    select a from T where b = 3

      |--Concatenation
         |--Index Seek(OBJECT:([ Т ].[ Тb ]), SEEK:([ Т ]. [ b ]=(1)) ORDERED FORWARD)
         |--Index Seek(OBJECT:([ Т ].[ Тb ]), SEEK:([ Т ]. [ b ]=(3)) ORDERED FORWARD)

    Оптимизатор не объединяет эти операции в одну операцию INDEX SEEK (поиска по индексу), но запросы и планы логически идентичны.
    Рассмотрим следующий запрос:

    select a from T where b = 1 or c < 3

      |--Clustered Index Scan(OBJECT:([Т].[Тa]), WHERE:([ Т ]. [ b ]=(1) OR [Т]. [ c ]<(3)))

    У нас имеются индексы на столбцах “b” и “c”, но оптимизатор их не использовал. Почему? Нам нужны все строки, удовлетворяющие любому из предикатов. Мы могли бы использовать индекс на столбце “b”, чтобы получить строки, удовлетворяющие предикату “b=1″, но при этом мы можем пропустить строки, которые удовлетворяют предикату “с<3″, и для которых “b!=1″. Например, мы пропустили бы строку со значением (2,2,2,2). Та же самая проблема возникает при использовании индекса на столбце “с”, чтобы удовлетворить предикату “c<3″. (В моем примере данные не включают строк со значением “b=1″, для которых выполняется предикат “с>3″, но такая строка могла бы существовать, поэтому мы должны предусмотреть и такой вариант).

    Индексные объединения

    И так, давайте разберёмся, будет ли SQL Server так выполнять декомпозицию запроса, чтобы использовать два индекса? Да! Сначала для того чтобы оптимизатор выбрал другой план, в котором не будет сканирования кластерного индекса, мы должны добавить достаточно большое количество данных в таблицу, чтобы сделать операцию сканирования кластерного индекса более дорогостоящей.
    Обратите внимание, что к используемой ранее таблице я добавил столбец типа char(200), чтобы строки стали больше. Добавление этого столбца приведет к тому, что таблица будет занимать больше страниц, что также сделает операцию просмотра более дорогостоящей.

    truncate table T

    set nocount on
    declare @i int
    set @i = 0
    while @i < 1000
      begin
        insert T values(@i, @i, @i, @i)
        set @i = @i + 1
      end

    select a from T where b = 1 or c < 3

      |--Sort(DISTINCT ORDER BY:([T]. [ а ]ASC))
         |--Concatenation
            |--Index Seek(OBJECT:([T].[Tb]), SEEK:([T]. [ b ]=(1)) ORDERED FORWARD)
            |--Index Seek(OBJECT:([T].[Tc]), SEEK:([T]. [ c ] < (3)) ORDERED FORWARD)

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

    select a from T where b = 1
    union
    select a from T where c < 3

    Однако, стоит обратить внимание на то, что два объединяемых запроса могут возвратить дубликаты строк, поэтому нужно использовать оператор UNION (который устраняет дубликаты), а не UNION ALL (который этого не делает). Оператор CONCATENATION реализует конструкцию UNION ALL, а SORT DISTINCT устраняет дубликаты, превращая UNION ALL в UNION. Такой тип плана исполнения запроса можно считать индексным объединением.

    Merge Join

    Давайте немного изменим запрос:

    select a from T where b = 1 or c = 3

      |--Stream Aggregate(GROUP BY:([Т]. [ a ]))
         |--Merge Join(Concatenation)
            |--Index Seek(OBJECT:([ Т ].[ Тb ]), SEEK:([ Т ]. [ b ]=(1)) ORDERED FORWARD)
            |--Index Seek(OBJECT:([Т].[Тc]), SEEK:([Т]. [ c ]=(3)) ORDERED FORWARD)

    Теперь, вместо операторов CONCATENATION и SORT DISTINCT мы имеем MERGE JOIN (Concatenation) и STREAM AGGREGATE. Что же произошло? MERGE JOIN (Concatenation) или “MERGE UNION” в действительности ничего не соединяют. Это осуществляется также, как с помощью итератора MERGE UNION, но в действительности выполняется операция UNION ALL с сохранением порядка входных строк. После этого используется STREAM AGGREGATE, который устраняет дубликаты (для получения более подробной информации, изучите статью “Агрегат потока (Stream Aggregate)“, в которой написано об использовании STREAM AGGREGATE для устранения дубликатов). Такой план исполнения запроса будет работать лучше, потому что не будет использовать SORT DISTINCT, который использует память и может стать причиной сброса страницы данных на диск, если действие выполняется за пределами памяти. В этом плане исполнения запроса используется STREAM AGGREGATE, который память не использует.

    Тогда почему же мы не использовали этот план с самого начала? Точно так же как MERGE JOIN, MERGE UNION требует, чтобы входные данные были отсортированы по ключу слияния (в нашем случае это столбец “a”). Некластеризованный индекс “Tb” покрывает ключ индекса “b” и ключ кластеризованного индекса “a”. Таким образом, этот индекс возвращает строки в порядке (b, a). Однако, это эквивалентно предикату “b = 1″, столбец “b” – константа, этим мы фактически упорядочиваем строки по столбцу “a”. То же самое случается с индексом Tc и предикатом “c = 3″. Таким образом, у нас имеется два входных потока, которые оба упорядочены по столбцу “а”, и мы можем использовать MERGE UNION.
    В предшествующем примере, одним из предикатов был “c 3″. Поскольку этот предикат – неравенство, INDEX SEEK возвращает строки в порядке (c, a). И так как строки не отсортированы по столбцу “a”, мы не сможем использовать MERGE UNION.

    Объединение трех индексов

    Оператор CONCATENATION может поддерживать больше двух входных потоков:

    select a from T where a = 1 or b = 2 or c < 3

      |--Sort(DISTINCT ORDER BY:([Т]. [ а ]ASC))
         |--Concatenation
            |--Clustered Index Seek(OBJECT:([Т].[Тa]), SEEK:([Т]. [ a ]=(1)) ORDERED FORWARD)
            |--Index Seek(OBJECT:([ Т ].[ Тb ]), SEEK:([ Т ]. [ b ]=(2)) ORDERED FORWARD)
            |--Index Seek(OBJECT:([Т].[Тc]), SEEK:([Т].[с] < (3)) ORDERED FORWARD)

    MERGE UNION поддерживает только два входных потока, но входные потоки можно объединять каскадом, чтобы позволяет в итоге объединить больше двух входных потоков:

    select a from T where a = 1 or b = 2 or c = 3

      |--Stream Aggregate(GROUP BY:([Т]. [ a ]))
         |--Merge Join(Concatenation)
            |--Merge Join(Concatenation)
            |   |--Clustered Index Seek(OBJECT:([Т].[Тa]), SEEK:([Т]. [ a ]=(1)) ORDERED FORWARD)
            |   |--Index Seek(OBJECT:([ Т ].[ Тb ]), SEEK:([ Т ]. [ b ]=(2)) ORDERED FORWARD)
            |--Index Seek(OBJECT:([Т].[Тc]), SEEK:([Т]. [ c ]=(3)) ORDERED FORWARD)

    Какие столбцы возвращает объединение?

    Объединение возвращает только те столбцы, которые являются общими для всех его входных потоков. Во всех приведенных выше примерах индексных объединений, единственным столбцом, который у индексов был общим, являлся ключ кластеризованного индекса – колонка “a” (это как если бы индекс Tb состоял из столбцов “b, a”, а индекс Tc состоял из “c, a”). Таким образом, наше объединение может вернуть только столбец “a”. Если будут запрошены и другие столбцы, будет использоваться BOOKMARK LOOKUP. Так будет даже в том случае, если один из индексов в объединении будет покрывающим ещё для каких-либо столбцов. Например, если мы запросим все три столбца “a”, “b” и “c”, в плане исполнения будет BOOKMARK LOOKUP, несмотря на то, что столбец “b” будет покрываться индексом Тb, а столбец “c” будет покрываться индексом Тc:

    select a, b, c from T where b = 1 or c = 3

      |--Nested Loops(Inner Join, OUTER REFERENCES:([Т]. [ a ]))
         |--Stream Aggregate(GROUP BY:([Т]. [ a ]))
            |   |--Merge Join(Concatenation)
            |      |--Index Seek(OBJECT:([ Т ].[ Тb ]), SEEK:([T]. [ b ]=(1)) ORDERED FORWARD)
            |      |--Index Seek(OBJECT:([Т].[Тc]), SEEK:([Т]. [ c ]=(3)) ORDERED FORWARD)
            |--Clustered Index Seek(OBJECT:([Т].[Тa]), SEEK:([Т]. [ a ]=[Т]. [ a ]) LOOKUP ORDERED FORWARD)

    Репликация программируемых объектов БД в SQL Server 2005

    Репликация SQL Server 2005/2008. Сборник статей от сообщества SQL.RU

    Под общей редакцией А. Гладченко и В. Щербинина.

    Москва. ЭКОМ Паблишерз, 2008Г. 288 страниц. ISBN: 978-5-9790-0086-2. Книга уже в продаже.

    Эта книга – сборник статей, которые посвящены ключевым для понимания репликации SQL Server моментам. Кроме переводов наиболее интересных зарубежных авторов, являющимисяхся признанными во всём мире специалистами, в книге вы найдёте ряд статей участников сообщества SQL.RU, которые многие годы помогаю решать разнообразные проблемы на технических форумах этого замечательного интернет – ресурса. Если во время прочтения книги у вас возникнут сомнения или вопросы по поводу изложенных тут материалов, смело обращайтесь к авторам статей на форумах SQL.RU, они с удовольствием вам помогут.
    В сборник включены переводы и статьи следующих участников сообщества SQL.RU: Дмитрий Артёмов, Александр Волок, Александр Гладченко, Ильдар Даутов, Григорий Кoрнилов, Алексей Ковалёв, Наталья Кривонос, Ян Либерман, Ирина Наумова и Владислав Щербинин. Причём, на момент написания книги, Александр Гладченко, Ян Либерман и Ирина Наумова являлись SQL Server MVP.
    Книга в первую очередь ориентирована на администраторов баз данных, которые собираются углубить свои познания в репликации SQL Server. Назначение этой книги состоит в том, чтобы предоставить читателям набор апробированных в течение нескольких лет рецептов по использованию и настройке репликации в SQL Server.

    ЗАКАЗАТЬ


    Обсудить книгу можно тут: http://www.sql.ru/forum/actualthread.aspx?tid=643700


    Пример статьи из сборника:


    Репликация программируемых объектов БД в SQL Server 2005


    По материалам статьи Байя Павлиашвили (Baya Pavliashvili) «Replicating Code Modules with SQL Server 2005».


    Перевод Ирины Николаевны Наумовой


    В предыдущих статьях из этой серии рассказывалось о том, как организовать в SQL Server 2005 репликацию статей представляющих собой таблицы. Как и предыдущие версии, SQL Server 2005 предоставляет возможность репликации модулей кода: хранимые процедуры, представления (включая индексированные представления) и пользовательские функции (UDF). В этой статье дается краткий обзор репликации программируемых объектов и даются рекомендации по использованию репликации таких модулей.


    Настройка репликации программируемых объектов.


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


      Тип статьи

      Причина

      Представление

      Таблицы, на которых базируется представление, должны существовать на подписчике. Однако эти таблицы могут не участвовать в репликации.

      Индексированное представление

      Таблицы, на которых базируется представление, должны существовать на подписчике. Однако эти таблицы не участвуют в репликации.

      На серверах – подписчиках должна быть установлена версия SQL Server 2000 и выше. Все подписчики должны использовать SQL Server в редакции Enterprise Edition.

      Хранимые процедуры, определяемые пользователем функции

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


    Для добавления в репликацию представлений, пользовательских функций и хранимых процедур можно воспользоваться мастером создания публикации. После того как вы откроете публикацию нужного типа для издаваемой базы данных, можно приступить непосредственно к работе со статьёй. На следующем рисунке продемонстрировано добавление к публикации представления, индексированного представления, хранимой процедуры и пользовательской функции.



    Рис.1.


    Чтобы выбрать опции для каждой добавляемой в публикацию статей, нужно нажать кнопку Article Properties. Можно выбрать несколько опций для каждого типа реплицируемых программируемых объектов. Также можно реплицировать схему представлений, индексируемых представлений и пользовательских функций. Для хранимых процедур предусмотрена дополнительная гибкость – помимо их определения вы можете реплицировать и их выполнение. В таблицу ниже сведены опции, доступные для настройки при репликации программируемых объектов.


      Тип статьи

      Опция/значение

      Описание

      Представления

      Copy User Triggers: True or False

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

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

      Copy Extended Properties: True or False

      На подписчике создаются расширенные свойства реплицируемого представления.

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

      Destination Object Name/Destination Object Owner

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

      Представления, индексированные представления, хранимые процедуры, определяемые пользователем функции.User Defined Function

      Action if name in use: Keep existing object unchanged OR Drop existing object and create a new one

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

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

      Create Schemas at Subscriber: True or False

      Определяет, должен ли выполняться оператор CREATE SCHEMA на подписчике, если там нет схемы объекта.

      Хранимые процедуры

      Replicate: Stored procedure definition only; Execution of the stored procedure; Execution in a serialized transaction of the SP.

      Определяет, должно ли реплицироваться выполнение хранимой процедуры.

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


    Запомните: Некоторые опции для статьи нельзя изменить после создания публикации. Например, опция Procedure Replicate не может быть изменена на Stored Procedure Definition, изменить её можно только на Execution Of The Stored Procedure. Чтобы изменить эту опцию, можно удалить статью и добавить ее заново, а затем уже изменить опцию. Так что перед настройкой репликации, определите заранее какие опции вам нужно будет установить.


    Как только Вы установили свойства для каждой статьи, можно создать снимок для публикации немедленно и/или создать расписание для запуска Snapshot Agent. Следующим шагом необходимо определить параметры настройки безопасности для Snapshot Agent и Log Reader agent, проанализируйте то, что должен сделать мастер, и нажмите кнопку Finish, чтобы создать публикацию.


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


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



    Рис.2.

    Примечание: Ошибки, возникающие при репликации программируемых объектов, обычно легко обнаружимы. Таких ошибок можно избежать, если внимательно читать экраны мастера и выполнить все требования.


    Следующий сценарий создает публикацию, в которую входят хранимая процедура, представление, индексированное представление и пользовательская функция:


      exec sp_addpublication @publication = n’pub_name’,
      @description=N’Transactional publication of database ’’AdventureWorksDW’’ .’,
      @sync_method = N’concurrent’, @retention = 0, @allow_push = N’true’,
      @allow_pull = N’true’, @allow_anonymous = N’true’,
      @enabled_for_internet = N’false’, @snapshot_in_defaultfolder = N’true’,
      @compress_snapshot = N’false’, @ftp_port = 21, @ftp_login = N’anonymous’,
      @allow_subscription_copy = N’false’, @add_to_active_directory = N’false’,
      @repl_freq = N’continuous’, @status = N’active’,
      @independent_agent = N’true’, @immediate_sync = N’true’,
      @allow_sync_tran = N’false’, @autogen_sync_procs = N’false’,
      @allow_queued_tran = N’false’, @allow_dts = N’false’, @replicate_ddl = 1,
      @allow_initialize_from_backup = N’false’, @enabled_for_p2p = N’false’,
      @enabled_for_het_sub = N’false’
      GO

      --Добавление статьей в публикацию репликации транзакций
      --Определяемая пользователем функция:
      exec sp_addarticle @publication = n’pub_name’,
      @article = N’udfMinimumDate’, @source_owner = N’dbo’,
      @source_object = N’udfMinimumDate’, @type = N’func schema only’,
      @description = N’’, @creation_script = N’’, @pre_creation_cmd = N’drop’,
      @schema_option = 0x0000000008000001, @destination_table = N’udfMinimumDate’,
      @destination_owner = N’dbo’, @status = 16
      GO

      --Хранимая процедура:
      exec sp_addarticle @publication = n’pub_name’,
      @article = N’update_factFinance’, @source_owner = N’dbo’,
      @source_object = N’update_factFinance’, @type = N’proc exec’,
      @description = N’’, @creation_script = N’’, @pre_creation_cmd = N’drop’,
      @schema_option = 0x0000000008000001, @destination_table = N’update_factFinance’,
      @destination_owner = N’dbo’, @status = 0
      GO
      --Индексированное представление:
      exec sp_addarticle @publication = n’pub_name’,
      @article = N’View_DimCustomer_Young’, @source_owner = N’dbo’,
      @source_object = N’View_DimCustomer_Young’, @type = N’indexed view schema only’,
      @description = N’’, @creation_script = N’’, @pre_creation_cmd = N’drop’,
      @schema_option = 0x0000000008000001, @destination_table = N’View_DimCustomer_Young’,
      @destination_owner = N’dbo’, @status = 16
      GO
      --Представление:
      exec sp_addarticle @publication = n’pub_name’,
      @article = N’vTimeSeries’, @source_owner = N’dbo’,
      @source_object = N’vTimeSeries’, @type = N’view schema only’, @description = N’’,
      @creation_script = N’’, @pre_creation_cmd = N’drop’,
      @schema_option = 0x0000000008000001, @destination_table = N’vTimeSeries’,
      @destination_owner = N’dbo’, @status = 16
      GO

    Изменение схемы репликации

    Вспомните, в предыдущей версии SQL Server для того, чтобы определения программируемых объектов передались подписчику, нужно было запустить агента создания снимка. В SQL Server 2005 это уже не так: репликация передает операторы ALTER VIEW, ALTER FUNCTION, ALTER PROCEDURE и ALTER TRIGGER подписчику в реальном времени. Опция репликации триггеров уже не является единственной возможностью для статей соответствующих типов, но она по-прежнему позволяет копировать триггеры, определенные на таблице или представлении в публикуемой базе данных. Запомните, что нельзя реплицировать DDL триггеры (триггеры языка определения данных).

    Давайте рассмотрим репликацию изменения индексируемого представления. Я создал очень простой пример индексируемого представления на издателе с помощью следующих команд:

      CREATE VIEW [dbo].[View_DimCustomer_Young]
      WITH SCHEMABINDING
      AS
      SELECT CustomerKey, GeographyKey, FirstName, LastName, BirthDate
      FROM dbo.DimCustomer
      WHERE (BirthDate > CONVERT(SMALLDATETIME, ’1/1/1980’, 101))
      GO

      CREATE UNIQUE CLUSTERED INDEX [ix_DCY_CustomerKey] ON [dbo].[View_DimCustomer_Young]
      (
      [CustomerKey] ASC
      )WITH (PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF)
      ON [PRIMARY]

    Это представление возвращает несколько столбцов таблицы DimCustomer для записей клиентов, которые родились после 1 января 1980 года. Я добавил это представление в публикацию репликации транзакций и создал для нее подписку на другом сервере.

    Теперь давайте изменим представление, чтобы оно возвращало клиентов, родившихся после 1 января 1978 года. Для этого я выполнил следующий код:

      ALTER VIEW [dbo].[View_DimCustomer_Young]
      WITH SCHEMABINDING
      AS
      SELECT CustomerKey, GeographyKey, FirstName, LastName, BirthDate
      FROM dbo.DimCustomer
      WHERE (BirthDate > CONVERT(SMALLDATETIME, ’1/1/1978’, 101))

    Теперь, если я в контексте базы данных распространителя выполняю системную хранимую процедуру sp_browserplcmds, я найду там команду ALTER VIEW, которая предназначена для передачи подписчику.

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

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


    Рис.3.


    Репликация индексируемых представлений как таблиц


    Репликация индексированных представлений осуществляется также как репликация таблиц; в этом случае, SQL Server создает таблицу на подписчике, которая содержит те же данные, что и индексированное представление на издателе. Данные изменяются в индексированном представлении на издателе и передаются в таблицу на подписчике. Обратите внимание, что таблица, на которой основано индексированное представление, не обязана существовать на подписчике.


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


    Для того чтобы выполнять репликацию индексированного представления как таблицы, нужно изменить параметр @type системной процедуры sp_addarticle. По умолчанию этот параметр установлен в значение N’indexed view logbased’. Например, следующий код добавит индексированное представление View_DimCustomer_Young в существующую публикацию как таблицу.


      exec sp_addarticle
      @publication = N’publication_name’,
      @article = N’View_DimCustomer_Young’,
      @source_owner = N’dbo’,
      @source_object = N’View_DimCustomer_Young’,
      @type = N’indexed view logbased’,
      @description = null,
      @creation_script = null,
      @pre_creation_cmd = N’none’,
      @schema_option = 0x0000000008000001,
      /* table name doesn’t have to be the same as view name */
      @destination_table = N’View_DimCustomer_Young’,
      @destination_owner = N’dbo’
      GO


    После настройки репликации индексированного представления как таблицы, операторы INSERT, UPDATE и DELETE, выполненные на представлении на издателе, будут реплицированы в таблицу на подписчике.


    Репликация выполнения хранимых процедур


    Таким же образом можно настроить репликацию выполнения хранимых процедур, что очень полезно при больших изменениях в имеющихся данных, и при условии, что данные на подписчике и издателе идентичны. Что произойдет, если выполнение оператора UPDATE затрагивает 1000 строк реплицируемой таблицы? По умолчанию SQL Server трансформирует одну команду UPDATE в выполнение хранимой процедуры репликации 1000 раз. Этот вариант хорош тем что каждое выполнение хранимой процедуры репликации затрагивает только одну строку что не вызывает большое количество блокировок/подтверждений на подписчике.


    Но что произойдет, если ваша хранимая процедура выполняет изменения, затрагивающие миллион строк в нескольких таблицах? Ваша база данных распределения будет расти экспоненциально, и время задержки репликации может стать недопустимо большим. Перед тем как передать эти команды подписчику SQL Server должен прочитать их из таблицы msrepl_commands базы данных распределения; Агент – чистильщик распределителя занимается удалением транзакции для этих таблиц, когда они уже были переданы подписчику. Если таблица msrepl_commands содержит несколько миллионов строк, чтение и удаление данных из этой таблицы будет выполняться очень медленно. Кроме того, передача больших изменений при использовании табличной статьи, оказывает большую нагрузку на сеть.


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


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


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


    Например, предположим что у нас есть хранимая процедура, которая изменяет некоторое количество строк в таблице factFinance базы данных AdventureWorksDW:


      CREATE PROC update_factFinance (
      @PercentChange NUMERIC (3,2),
      @OrganizationKey TINYINT,
      @TimeKey INT)
      AS

      /*
      Изменяем количество выданных ключей
      */
      UPDATE factFinance
      SET amount = amount * @PercentChange
      WHERE OrganizationKey = @OrganizationKey
      AND TimeKey = @TimeKey


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


      {call "dbo"."update_factFinance " (1.10, 3, 32)}


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


    Репликация выполнения хранимых процедур внутри сериализуемой транзакции.


    Хранимую процедуру можно включить в репликацию в качестве статьи, только если она выполняется внутри сериализуемой транзакции. Это требует соблюдения двух условий, обеспечить которые необходимо перед тиражированием процедуры подписчику:


    • Уровень изоляции транзакций у подключения, в котором выполняется хранимая процедура, должен быть установлен в SERIALIZABLE.
    • Необходимо выполнять процедуру внутри явной транзакции, используя операторы BEGIN TRANSACTION / COMMIT TRANSACTION.

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


    Репликация выполнения хранимых процедур внутри сериализуемой транзакции – это рекомендуемая опция, когда необходимо поддержать целостность данных на издателе и подписчике. Почему? В каждой хранимой процедуре содержится несколько явных или неявных транзакций. Вы можете столкнуться с ситуацией когда некоторые транзакции внутри хранимой процедуры завершаются успешно, а другие с ошибкой. Если Вы заставляете SQL Server реплицировать каждое выполнение хранимой процедуры, тогда даже то выполнение, в котором транзакции завершаются ошибками, будет отправлено подписчику. Уровень изоляции транзакций – SERIALIZABLE, является самым жестким уровнем изоляции, гарантирующим, что блокировки будут установлены на всех таблицах, которые использует хранимая процедура. Блокировки будут удерживаться до тех пор, пока транзакция не будет завершена. Поэтому, использование в репликации выполнение только в пределах сериализуемой транзакции, дает гарантию того, что процедура успешно завершит работу на издателе, и только потом будет послана подписчикам.


    Давайте воспользуемся процедурой update_factFinance чтобы продемонстрировать как мы можем реплицировать ее выполнение внутри сериализуемой транзакции. Выполним следующий код:


      SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
      SET XACT_ABORT ON
      BEGIN TRAN
      EXEC update_factFinance 1.10, 3, 32

      COMMIT


    Однако, следующая команда не будет реплицирована, потому что она не включена в явную транзакцию:


      EXEC update_factFinance 1.10, 3, 32


    Теперь мы завершим выполнение процедуры, чтобы продемонстрировать значение установки XACT_ABORT. Я изменяю тип данных столбца amount таблицы factFinance на SMALLINT, вместо INT, выполняя следующую инструкцию:

      ALTER TABLE factFinance ALTER COLUMN amount SMALLINT

    Максимальное значение для типа SMALLINT – 32768; умножаем максимальное значение столбца на 1.15 чтобы результат превысил 32768, таким образом, следующее выполнение процедуры update_factFinance, приведет к ошибке:

      SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
      /* CORRECT setting*/
      SET XACT_ABORT ON
      BEGIN TRAN
      EXEC update_factFinance 1.15, 3, 32

      COMMIT

    Результат таков:

      Msg 8115, Level 16, State 2, Procedure update_factFinance, Line 10
      Arithmetic overflow error converting expression to data type smallint.

    Транзакция отменена, и выполнение хранимой процедуры не передано подписчику.

    Далее, выполним тот же набор команд, отменив установку XACT_ABORT:

      SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
      /* НКОРРЕКТНАЯ УСТАНОВКА! Это сделано только в демонстрационных целях! */
      SET XACT_ABORT OFF
      BEGIN TRAN
      EXEC update_factFinance 1.15, 3, 32

      COMMIT

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

      {call "dbo"."update_factFinance" (1.15,3,32)}

    Выполнение процедуры передано подписчику даже при условии того, что на издателе оно завершилось с ошибкой. Это приведет к тому, что агент распределения завершит работу с ошибкой. И что еще более важно, может нарушить целостность данных на подписчике и издателе. Поэтому всегда используйте опцию SET XACT_ABORT ON при репликации выполнения хранимых процедур.

    Вывод

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

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

    SQL Internals Viewer

     


    •  
    • Это еще одна полезная и бесплатная утилита для работы с SQL Server. Показывает внутреннюю структуру хранения данных: распределение страниц в памяти и размещение данных на страницах.

       

      Скачать утилиту можно по следующей ссылке:

      http://www.sqlinternalsviewer.com/download.html

       

      Системные требования:

    • Windows 2000, Windows XP, Windows Vista or Windows 2003 Server

    • .NET Framework 2.0

    • Microsoft SQL Server 2005 или Microsoft SQL Server 2008 July CTP. Эта программа не работает с SQL Server 2000.


    SQL-Internals-Viewer