Поиск узких мест ввода-вывода для 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...

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

По материалам статьи 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)

Posted by Ирина Наумова | with no comments
Filed under:

Репликация программируемых объектов БД в 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 – это зрелая технология, которая способна поддержать приложения класса предприятия. Подобно любой технологии, репликация работает хорошо, если используется к месту и решает присущие ей задачи. Убедитесь, что изучили тонкости работы репликации, и постарайтесь очень тщательно спланировать внедрение решений с ее использованием.

Posted by Ирина Наумова | with no comments
Filed under:

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 

Терминологический словарик.

  Часто возникает не1201856078_karобходимость перевести тот или иной технический термин, относящийся к какому-либо программному продукту на свой родной язык.  Теперь сделать это станет намного проще, поскольку на сайте Майкрософт появилась возможность "подсмотреть" перевод нужного термина. Для этого необходимо ввести термин на английском языке, выбрать продукт из выпадающего списка и язык перевода.

Подборка общих решений по использованию T-SQL.

Недавно на сайте MSDN появился WIKI, где сотрудники Майкрософт и модераторы форумов MSDN публикуют  примеры решений и ответы на распространённые вопросы по использованию  T-SQL.  Информация в этом WIKI представляет собой предложения, идеи и мнения людей, отвечающих на вопросы посетителей форумов MSDN по SQL Server,  оформлена она в виде статей. Имеются следующие разделы:

  • How To Examples of selected T-SQL programming constructs
  • How To Troubleshoot and Debug T-SQL Code
  • Writing Transact-SQL Statements Tutorial

Появление новых материалов удобно отслеживать, подписавшись на  новостную ленту RSS.

 

BOL. «Пометки на полях»

Сайт MSDN2 вместе с электронной документацией по Visual Studio 2005/2008 и SQL Server 2005/2008 содержит ещё и встроенный «MSDN Library WiKi». На страницах WiKi содержатся примечания, примеры или пояснения к статьям документации. Недавно стало возможно делать такие пометки и для переведённого на русский язык варианта BOL 2005. В русском переводе WiKi получили название «Содержимое сообщества». Подобно блогам, русскоязычное содержимое может сопровождаться тематическими тегами и имеет свой веб-канал, в котором можно отслеживать последние изменения:  http://msdn2.microsoft.com/ru-ru/library/community-edits(rss).aspx

Для того, чтобы оставлять свои комментарии к статьям BOL, нужно зарегистрироваться. Для регистрации используется LiveID. После этого, на MSDN2 будет создан ваш профиль и другие участники WiKi или читатели интернет – версии BOL смогут видеть список ваших заметок и даже подписаться на новостной Веб-канал вашего профиля.

Наиболее активные и ценные соавторы попадают на «доску почёта», которая доступна на домашней странице WiKi. Судя по активности WiKi на других языках, оказаться на такой доске почёта очень достойно ;) Пока пальму первенства там прочно удерживают сотрудники Майкрософт.

Posted by Ирина Наумова | with no comments
Filed under:

SQL job manager от Idera.

maingraphic_large_JM

 

   В качестве новогоднего подарка для специалистов по SQL Server компания Idera предоставила возможность до 31 декабря бесплатно скачать их новый продукт - SQL job manager. Он позволяет отслеживать задания с нескольких серверов. Имеет ряд фильтров по состоянию выполнения заданий. Позволяет "перетаскивать" задания с одного сервера на другой. 

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

Ссылка на страницу SQL job manager

Еще раз о дефрагментации...

На Microsoft TechNet появилась еще одна интересная статья, затрагивающая проблему фрагментации данных. В ней описано почему появляется фрагментация и как с ней бороться, в том числе и про использование дополнительных методов: PageDefrag (дефрагментирует файлы, заблокированные операционной системой) и Contig (предоставляет возможность дефрагментации отдельного файла)

Ссылка на статью

Бесплатные тесты по SQL Server 2005.

register 

 На сайте www.brainbench.com до 30 сентября предоставлена возможность бесплатно пройти тесты по множеству продуктов, включая и SQL Server 2005. По SQL Server 2005 доступны следующие тесты:

MS SQL Server 2005 Programming

MS SQL Server 2005 Administration Beta

 Для того, чтобы пройти тестирование, необходимо зарегистрироваться.  В поле "Enter your promo code (optional)" ничего вводить не нужно, можно сразу нажать кнопку <Get Test>.

Дефрагментация баз данных SQL Server с помощью утилиты Diskeeper.

  Howard Butler Sr.,Systems Engineer; Deskeeper

  Michael Materie,Systems Engineer MCSE,CCNA,A+,I-Net+ 

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

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

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

Базы данных SQL Server представляют собой большие файлы базы данных и журналов, для которых во время создания резервируется некоторый размер. Если при создании этих файлов на диске есть непрерывный, не занятый и достаточный по размеру отрезок, они не будут фрагментированы. Но если доступное свободное место не является непрерывным, то уже изначально база данных и журналы будут фрагментированы. Даже если первоначально база данных и журналы не фрагментированы, после их создания, они почти наверняка станут фрагментированными, поскольку база данных все время растет. Например, если Вы устанавливаете первоначальный размер базы данных равным 100 МБ, а файл журнала 10 МБ, и установили следующие параметры автоматического прироста файлов: до 5Гб файл данных и до 100 МБ файл журнала, внешняя фрагментация может быть большой. Каждый раз, когда файлы данных или журналов автоматически увеличиваются, появляется угроза внешней фрагментации.

Для устранения внешней фрагментации используется специализированная утилита операционной системы. Одним из самых популярных инструментов для дефрагментации файлов базы данных SQL Server является Deskeeper  от Executive Software. Diskeeper существует уже много лет, и многие из Вас возможно уже знакомы с ним, и не только в работе с Windows, но и в работе с серверами печати. А вот что не известно многим DBA, так это то, что Deskeeper - лучший инструмент для устранения внешней фрагментации на их серверах с SQL Server. Работа утилиты по устранению внешней фрагментации, подобной Diskeeper, не реструктурирует внутреннее содержание файла, в отличие от DBCC REINDEX. После того как Diskeeper устранит фрагментацию файла, этот не фрагментированный файл будет точным дубликатом оригинала. Поскольку свободные места внутри базы данных у не фрагментированного файла от этой операции не исчезнут, Вам нужно будет время от времени проводить реиндексацию, устраняющую именно внутреннюю фрагментацию страниц данных и индексов.

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

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

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

Всеми перемещениями файлов при работе утилиты Diskeeper во время дефрагментации непосредственно управляет операционная система. Фактически, код операционной системы, выполняющий эту функцию, который первоначально был написан Executive Software, распределяет по приоритетам безопасности что может быть дефрагментировано, а что нет. Файлы SQL Server (например .LDF и .MDF) абсолютно безопасно дефрагментировать. Если в тот момент, когда Diskeeper посылает запрос операционной системе (посредством программного интерфейса - API) на перемещение файлов, ему попадутся файлы, которые не могут быть безопасно перемещены, он через них просто перескакивает без сообщений об ошибке или иных сигналов.

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

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

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

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

Статья взята с SQL-Server-Performance.Com © 2000 - 2004 SQL-Server-Performance.Com

Все права зарезервированы. Executive Software International- зарегистрированные торговые знаки или торговые знаки, принадлежащие Executive Software International, Inc. Все другие торговые знаки принадлежат их владельцам.

В дополнение к статье.

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

    http://technet.microsoft.com/en-us/sysinternals/bb897428.aspx

  • Перевод статьи Мориса Льюиса "Победа над фрагментацией (документация)", в которой описано как обнаружить фрагментацию в SQL Server и как с ней бороться:

    http://www.interface.ru/home.asp?artId=7047

  • Как происходит фрагментация файлов в операционных системах Windows XP / Windows Server 2003

    http://www.winblog.ru/admin/1147764772-14120703.html

System Center Data Protection Manager Beta 2

        scheme DPM На англоязычном сайте Майкрософт выложена версия System Center Data Protection Manager Beta 2.  Продукт предназначен для централизованного резервного копирования и восстановления файлов.
        Ресурс на русском языке, посвященный System Center Data Protection Manager, находится здесь.  Также на сайте можно получить дополнительную информацию о работе DMP, например, по работе с SQL Server имеются следующие материалы:

      Автообновление SQL Server Books Online 2005.

      Как сообщил Бак Вуди в своем блоге, теперь SQL Server 2005 Books Online будет автоматически обновляться через службу Microsoft Update, правда пока только для английской редакции. В списке компонент, предлагаемых к установке появится февральская версия BOL (Doc Refresh 4). Скриншот установки электронной документации через Microsoft Update можно посмотреть в блоге Гленна Берри.

      Поскольку полная версия BOL занимает порядка 150 МВ, Microsoft планирует обновлять электронную документацию примерно раз в квартал. А текущие автообновления будут представлены небольшими модулями.

      Сначала обновляется Web-версия BOL. Насколько я поняла, помимо автообновления через Microsoft Update, предусмотрен также механизм частичной синхронизации статей справки  с текущей версией, то есть если пользователь обращается к статье, версия которой устарела,  происходит загрузка новой версии с Web. Предусмотрено два способа такой загрузки: принудительная - pull и пассивная - push. То, какой тип загрузки будет использован, зависит от выбранных параметров справки в сети, которые определяют приоритет проверки содержимого справки:

      • Try online first, then local (Проверить сначала сетевую версию, затем локальную)

      • Try local first, then online (Проверить сначала локальную версию, затем сетевую)

      AdventureWorks Light

      В феврале Microsoft включила в состав поставляемых к SQL Server 2005 примеров: SQL Server 2005 Samples and Sample Databases (February 2007) облегчённую версию учебной базы данных AdventureWorksLT.msi, размер которой в дистрибутиве составляет 2251КБ.
      Схема облегчённой базы проще, а саму базу легче развернуть и использовать для демонстрации примеров. Появление AdventureWorksLT было с энтузиазмом встречено в сообществе специалистов SQL Server, вот несколько ссылок на сообщения об этом событии в блогах: