Оптимизация работы серверов баз данных Microsoft SQL Server 2005

Повышение производительности запроса

Разбить на страницы
Показывать лекцию целиком

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

Планы запросов

Когда сервер SQL Server выполняет запрос, сначала требуется определить наилучший способ выполнения. Для этого нужно рассчитать, как и в каком порядке обращаться к данным и соединять их, как и когда выполнять вычисления и агрегации и т. д. За это отвечает подсистема, которая называется Query Optimizer (Оптимизатор запроса). Оптимизатор запроса использует статистические данные о распределении данных, метаданные, относящиеся к объектам в базе данных, информацию индекса и другие факторы для вычисления нескольких возможных планов выполнения запроса. Для каждого из этих планов Оптимизатор запроса предполагает его стоимость на основе статистики по этим данным и выбирает план с минимальными затратами ресурсов на выполнение. Конечно, SQL Server не вычисляет всех возможных планов для каждого запроса, поскольку для некоторых запросов сами эти вычисления могут отнять больше времени, чем выполнение наименее эффективного из всех планов. Следовательно, SQL Server использует сложные алгоритмы, чтобы найти план выполнения с разумной стоимостью, близкой к минимально возможной. После того, как план выполнения сгенерирован, он хранится в буферном кэше (на что SQL Server выделяет большую часть своей виртуальной памяти). Затем план выполняется тем способом, который Оптимизатор запроса сообщает ядру базы данных (компоненту database engine).

Примечание. Планы выполнения в буферном кэше могут быть повторно использованы при выполнении такого же или аналогичного запроса. Следовательно, планы выполнения хранятся в кэше максимально возможное время. Дополнительную информацию о кэшировании планов выполнения см. в официальном документе под названием: "Batch Compilation, Recompilation, and Plan Caching Issues in SQL Server 2005" (Проблемы компиляции и рекомпиляции пакетов, а также кэширования планов в SQL Server 2005) на странице http://www.microsoft.com/ technet/prodtechnol/sql/2005/recomp.mspx.

Сможет ли Query Optimizer (Оптимизатор запросов) сгенерировать эффективный план для конкретного запроса, зависит от следующих аспектов:

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

    Примечание. Конечно, возможно, что неэффективно выполненный запрос выполнялся в соответствии с хорошим планом. В этих случаях дело не в оптимизации запроса. Скорее всего, проблема кроется совсем в другом, например, в проекте запроса, конфликте доступа к данным, операций ввода/вывода, памяти, использования ЦПУ, сетевых ресурсов и т. п. Чтобы получить дополнительную информацию по этим проблемам, рекомендуем ознакомиться с официальным документом "Troubleshooting Performance Problems in SQL Server 2005" (Поиск и решение проблем с производительностью в SQL Server 2005), который доступен по следующей ссылке: http://www.microsoft.com/ technet/prodtechnol/sql/2005/tsprfprb.mspx.

    Знакомимся с планами выполнения запросов

  • В меню Start (Пуск) выберите All Programs,. Microsoft SQL Server 2005, SQL Server Management Studio (Все программы, Microsoft SQL Server 2005, Среда SQL Server Management Studio). Нажмите кнопку New Query (Создать запрос), чтобы открыть окно нового запроса, и измените контекст выполнения на базу данных Adventure Works, выбрав ее из раскрывающегося списка Available Databases (Доступные базы данных).
  • Выполните следующую инструкцию SELECT. Код этого примера имеется в файлах примеров под именем Viewing Query Plans.sql.
    SELECT SalesOrderID, OrderQTY 
    FROM Sales.SalesOrderDetail 
    WHERE ProductID = 712 ORDER BY OrderQTY DESC
  • Чтобы вывести на экран план выполнения для этого запроса, нажмите комбинацию клавиш (Ctrl+L) или выберите из меню Query (Запрос) команду Display Estimated Execution Plan (Показать предполагаемый план выполнения). План выполнения показан на следующем рисунке.

    При генерировании предполагаемого плана запроса запрос на самом деле не выполняется. Он только оптимизируется Оптимизатором запроса. Эта особенность Оптимизатора запросов является преимуществом, когда приходится иметь дело с запросами, которые имеют продолжительные рабочие циклы, ведь для того, чтобы увидеть план выполнения запроса, нет необходимости выполнять сам запрос. Графическое представление плана выполнения запроса читается справа налево и сверху вниз. Каждый значок в плане представляет один оператор, а данные, изменяемые между этими операторами, обозначены стрелками. Толщина стрелок соответствует объему данных, которые передаются между операторами. Мы не будем углубляться в подробности и объяснять значение каждого оператора; расскажем только о тех из них, которые показаны в данном плане выполнения запроса.

  • SQL Server обращается к данным при помощи операции Clustered Index Scan (Просмотр кластеризованного индекса). Это сканирование представляет собой реальную операцию доступа к данным и подробно рассматривается далее.
  • Данные переходят к оператору Sort (Сортировка), который сортирует данные на основе предложения ORDER BY.
  • Данные пересылаются клиенту.
  • Мы рассмотрим самые важные операторы, которые использует SQL Server, когда будем изучать индексы и соединения. Полный список операторов можно найти в Электронной документации SQL Server 2005, тема "Пиктограммы графического представления плана выполнения".

    Стоимость в процентах под пиктограммой каждого оператора показывает процент от общей стоимости запроса, представленного на графической схеме. Это число поможет вам понять, какая операция использует при выполнении больше всего ресурсов. В нашем случае самой дорогостоящей операцией является Clustered Index Scan (Просмотр кластеризованного индекса), которая составляет 89% общей стоимости запроса.

  • Задержите указатель мыши над оператором

    В этом окне отображается подробная информация об операции. До сих пор мы знали только то, что SQL Server извлекает данные при помощи операции сканирования. Но в этом окне видно, что он выполняет операцию Clustered Index Scan (Просмотр кластеризованного индекса) (которая подробно рассматривается ниже) на кластеризованном индексе таблицы Sales.SalesOrderDetail, а также поиск ProductID 712. Эта информация находится в секции Predicates (Предикаты). Кроме того, показаны предполагаемая стоимость и предполагаемое количество строк, а также размер строки. В то время, как количество строк оценивается на основе статистики, которую SQL Server хранит для этой таблицы, значения стоимости вычисляются на основе статистики и значений эталонной системы. Следовательно, значения стоимости не следует использовать для того, чтобы рассчитать, сколько времени запрос будет выполняться на компьютере. Эти цифры могут использоваться только для выявления более дешевой или более дорогостоящей операции.

  • Эту информацию об операторах можно увидеть также в окне Properties (Свойства) в SQL Server Management Studio. Чтобы открыть окно Properties (Свойства), щелкните правой кнопкой мыши на значке оператора и выберите из контекстного меню команду Properties (Свойства).
  • Планы запросов можно также сохранить. Чтобы сохранить план запроса, щелкните в панели плана правой кнопкой мыши и выберите из контекстного меню команду Save Execution Plan As (Сохранить план выполнения как). План сохраняется в формате XML с расширением .sqlplan. Его можно открыть через SQL Server Management Studio. выбрав из меню File (Файл) команды Open, File (Открыть, Файл).
  • То, что вы видели до сих пор - это предполагаемый план выполнения запроса, но можно просмотреть и действительный план выполнения. Действительный план выполнения аналогичен предполагаемому плану выполнения, но включает также действительные (не предполагаемые) значения количества строк, количества перемоток и т. д. Чтобы включить в запрос действительный план выполнения, нажмите (Ctrl+M) или выберите из меню Query (Запрос) команду Include Actual Execution Plan (Включить действительный план выполнения). Затем нажмите F5 и выполните запрос. Результаты запроса отображаются как обычно, но вы увидите также план выполнения, который показан на вкладке Execution Plan (План выполнения).
  • Создание индексов для обеспечения более быстрого выполнения запросов

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

    Структуры кучи

    Файл данных в базе данных SQL Server делится на страницы по 8 Кбайт. Каждая страница содержит данные, индексы или другие типы данных, которые нужны SQL Server, чтобы манипулировать файлами данных. Однако большая часть страниц - это страницы данных или индекса. Страницы представляют собой единицы, которые SQL Server считывает и записывает из файлов данных. Каждая страница содержит данные или информацию индекса только для одного объекта базы данных. Поэтому на каждой странице данных вы найдете данные об одном объекте, а на каждой странице индекса - только информацию индекса. В SQL Server 2000 невозможно разбить строки данных на несколько страниц; это означает, что строка данных должна уместиться на странице, что ограничивает размер строки примерно 8060 байт (за исключением больших объектов данных). В SQL Server 2005 это ограничение больше не существует для типов данных переменной длины, таких, как nvarchar, varbinary, CLR и т. п. Благодаря типам данных переменной длины строки могут занимать несколько страниц, но все строки с типом данных фиксированной длины все же должны вписываться в одну страницу.

    Когда пользователь создает таблицу и вносит в нее данные, SQL Server осуществляет поиск неиспользуемых страниц, на которых можно сохранить данные. Чтобы отслеживать, какие страницы содержат данные для таблиц, SQL Server для каждой таблицы хранит еще одну или больше дополнительных страниц IAM, Index Allocation Map (Карта распределения индекса). Эти IAM-страницы указывают на страницы, на которых хранятся данные. Поскольку данные для этих таблиц хранятся на страницах без индекса, то есть, их объединяют только IAM-страницы, такие таблицы называются кучами. Чтобы обратиться к данным в куче, SQL Server должен прочитать IAM-страницу этой таблицы, а затем просмотреть страницы, на которые ссылается IAM-страница. Эта операция называется просмотром, или сканированием, таблицы. При просмотре таблицы данные считываются не по порядку. Если запрос выполняет поиск какой-либо одной определенной строки, то операции просмотра таблицы кучи приходится читать все строки в таблице только для того, чтобы найти нужную строку. Эта операция очень неэффективна.

    Изучаем структуры кучи

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • В следующем примере мы создадим две таблицы с именами dbo.Orders и dbo.OrderDetails. Для создания таблиц и заполнения их данными ведите и выполните следующие инструкции. Код для всего примера можно найти среди файлов примеров под именем Examining Heap Structures.sql.
    USE AdventureWorks; 
    GO
    CREATE TABLE dbo.Orders( SalesOrderID int NOT NULL, 
        OrderDate datetime NOT NULL, 
        ShipDate datetime NULL, 
        Status tinyint NOT NULL,
        PurchaseOrderNumber dbo.OrderNumber NULL, 
        CustomerID int NOT NULL, 
        ContactID int NOT NULL, 
        SalesPersonID int NULL 
        );
    CREATE TABLE dbo.OrderDetails( SalesOrderID int NOT NULL, 
        SalesOrderDetailID int NOT NULL, 
        CarrierTrackingNumber nvarchar(25), 
        OrderQty smallint NOT NULL, 
        ProductID int NOT NULL, 
        UnitPrice money NOT NULL, 
        UnitPriceDiscount money NOT NULL,
        LineTotal AS (isnull((UnitPrice*((1.0)-UnitPriceDiscount))*OrderQty,(0.0)))
        );
    INSERT INTO dbo.Orders
    SELECT SalesOrderID, OrderDate, ShipDate, Status, PurchaseOrderNumber,
        CustomerID, ContactID, SalesPersonID 
      FROM Sales.SalesOrderHeader;
    
    INSERT INTO dbo.OrderDetails(SalesOrderID, SalesOrderDetailID, 
                CarrierTrackingNumber, OrderQty,
                ProductID, UnitPrice, UnitPriceDiscount) 
    SELECT SalesOrderID, SalesOrderDetailID,CarrierTrackingNumber,OrderQty,
           ProductID, UnitPrice, UnitPriceDiscount 
      FROM Sales.SalesOrderDetail;
  • Мы создали две таблицы со структурой хранения "куча". Для выполнения запроса к таблице dbo.Orders введите инструкции, которые приводятся ниже. Включите действительный план выполнения, нажав (Ctrl+M) до начала выполнения или выбрав из меню Query (Запрос) команду Include Actual Execution Plan (Включить действительный план выполнения). Выполните запрос:
    SET STATISTICS IO ON; SELECT * FROM dbo.Orders
    SET STATISTICS IO OFF

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

  • Перейдите на вкладку Messages (Сообщения). Вы увидите примерно такое сообщение:

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

  • Перейдите на вкладку Execution Plan (План выполнения). В плане выполнения, показанном на следующем рисунке, мы видим, что SQL Server использовал операцию Table Scan (Просмотр таблицы) для доступа к данным, как единственный возможный вариант.
  • Теперь немного изменим запрос, чтобы он возвратил указанные строки.
    SET STATISTICS IO ON;
    SELECT * FROM dbo.Orders WHERE SalesOrderID =46699;
    SET STATISTICS IO OFF;
  • Посмотрим вывод в виде сообщения и графическое представление плана выполнения. Вы видите, что для этого запроса SQL Server все еще требуется 178 считываний страниц и использование операции Table Scan (Просмотр таблицы). Просмотр таблицы используется потому, что SQL Server не располагает индексом, и, следовательно, ему необходимо просмотреть все данные, чтобы найти нужную строку. p>Мы видим, что SQL Server использует операции просмотра таблиц для доступа к таблицам, не имеющим индекса. Эти просмотры вынуждают SQL Server просматривать все данные независимо от размера таблицы. Если таблица очень велика, то для ее просмотра может потребоваться много времени.
  • Индексы в таблицах

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

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

    Индексы в SQL Server встроены в древовидную структуру, которая называется сбалансированным деревом. Основная структура сбалансированного дерева показана ниже в примере. Как видите, нижний уровень называется уровнем листовых вершин. Уровень листовых вершин можно мыслить как оглавление книги. Он содержит по одной записи на каждую строку данных, причем записи сортируются по столбцу индекса. Чтобы ускорить поиск значений в индексе, дерево построено поверх него с использованием операций сравнения < (меньше чем) и > (больше чем). Число уровней индекса зависит от количества записей и размера ключа индекса. В реальных условиях страница индекса могла бы содержать намного больше значений, чем изображено в примере. Поскольку страница имеет размер 8 Кбайт, SQL Server может указать на странице индекса на тысячи страниц. Следовательно, индекс обычно не имеет много уровней, даже если таблица содержит миллионы строк. Этот факт способствует очень быстрому поиску определенных значений.

    Надписи:
    Root (Level 2) - Уровень корневой вершины (2 уровень)
    Intermediate (Level 1) - Уровень внутренних вершин (1 уровень)
    Leaf Level (Level 0) - Уровень листовых вершин (0 уровень)
    KEY Pointer - Ключ-указатель

    Как уже отмечалось ранее, в SQL Server используется два типа индексов: кластеризованный и некластеризованный. Оба типа индексов представляют собой сбалансированные деревья, но построены они по-разному. Давайте посмотрим, в чем заключается разница.

    Кластеризованные индексы

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

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

    CREATE [ UNIQUE ] CLUSTERED INDEX index name 
      ON <object> ( column [ ASC | DESC ] [ ,...n ] )

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

    Примечание. SQL Server создает уникальный индекс, если для таблицы определено ограничение первичного ключа, или уникальности. Если определен первичный ключ, то он создает кластеризованный индекс по умолчанию, если такой индекс до сих пор не существует в таблице. Этот вид индекса в SQL Server должен использоваться, если создание первичного, или уникального, ограничения, может быть определено в инструкциях CREATE или ALTER TABLE с ключевыми словами CLUSTERED или NONCLUSTERED.

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

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Для создания уникального кластеризованного индекса в таблице Orders ведите и выполните следующую инструкцию. Код этого примера имеется в файлах примеров под именем Creating And Using Clustered Indexes.sql.
    CREATE UNIQUE CLUSTERED INDEX CLIDX_Orders_SalesOrderID 
        ON dbo.Orders(SalesOrderID)
  • Теперь выполните эти же две инструкции SELECT, как делали это раньше, и изучите различия. Обязательно включите при выполнении запроса действительный план выполнения.
    SET STATISTICS IO ON;
    
    SELECT * FROM dbo.Orders;
    SELECT * FROM dbo.Orders 
      WHERE SalesOrderID =46699;
    
    SET STATISTICS IO OFF;
  • Перейдите на вкладку Execution Plan (План выполнения).

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

  • Просмотр индекса.Просмотр уровня листовых вершин индекса со считыванием данных таблицы. Поскольку первая инструкция SELECT не имеет предложения WHERE, серверу SQL Server известно, что нужно возвратить все данные, которые хранятся на уровне листовых вершин индекса.
  • Поиск по индексу.Операция, при которой SQL Server выполняет поиск определенного значения, проходя по ветвям индекса, начиная от корневой вершины индекса.
  • Эти две операции могут также соединяться, чтобы извлечь определенный диапазон данных. В этом виде операции частичного просмотра SQL Server пытается найти начало диапазона, а затем просматривает дерево до конца этого диапазона.

  • Перейдите на вкладку Messages (Сообщения), как показано ниже.

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

  • Введите следующую инструкцию SELECT, которая возвращает данные в отсортированной форме, и нажмите (Ctrl+L), чтобы получить предполагаемый план выполнения.
    SELECT * FROM dbo.Orders ORDER BY SalesOrderID;
    SELECT * FROM dbo.Orders ORDER BY OrderDate;

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

    Во втором запросе данные были отсортированы после получения. Следовательно, после операции просмотра кластеризованного индекса имела место операция Sort (Сортировка), которая отсортировала данные по столбцу OrderDate. Поскольку сортировка является очень дорогостоящей операцией, второй запрос генерирует 93% общей стоимости обоих запросов. Таким образом, имеет смысл определять кластеризованный индекс в столбцах, которые часто используются в качестве аргумента сортировки или группировки критериев в агрегате, поскольку агрегация данных требует, чтобы SQL Server сначала сортировал данные в соответствии с критериями группировки.

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

  • Создаем составной кластеризованный индекс

  • В SQL Server Management Studio введите и выполните следующие инструкции, чтобы создать составной кластеризованный индекс в таблице OrderDetails.
    CREATE UNIQUE CLUSTERED INDEX CLIDX_OrderDetails 
      ON dbo.OrderDetails(SalesOrderID,SalesOrderDetailID)
  • Теперь введите еще две инструкции SELECT. Первая будет выполнять поиск указанного значения SalesOrderID, а вторая -указанного значения SalesOrderDetailID. Оба столбца являются индексными столбцами нашего индекса CLIDX_OrderDetails. Нажмите (Ctrl+L), чтобы отобразить предполагаемый план выполнения.
    SELECT * FROM dbo.OrderDetails 
       WHERE SalesOrderID = 46999 
    SELECT * FROM dbo.OrderDetails 
       WHERE SalesOrderDetailID = 14147
  • Легко заметить, что в первом запросе, который выполняет поиск значения из первого столбца составного индекса, SQL Server для поиска строки использует поиск по индексам (метод seek). Во втором запросе он использует просмотр индекса, который является более дорогостоящей операцией. Просмотр индекса используется потому, что невозможно искать значение только во втором столбце составного индекса, ведь индекс изначально сортируется по первому столбцу. Следовательно, важно продумать порядок индексных столбцов в составном индексе. Запомните, что составной индекс следует использовать только тогда, когда поиск по дополнительным столбцам выполняется исключительно в сочетании с первым столбцом или если должна быть применена уникальность.

    Некластеризованные индексы

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

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

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

    CREATE [ UNIQUE ] NONCLUSTERED INDEX index name 
        ON <object> ( column [ ASC | DESC ] [ ,...n ] )
  • Создаем и применяем некластеризованные индексы

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите следующую инструкцию SELECT и нажмите клавиатурную комбинацию (Ctrl+L), чтобы отобразить предполагаемый план выполнения. Код этого примера имеется в файлах примеров под именем Creating And Using Nonclustered Indexes.sql.
    SELECT DISTINCT SalesOrderID, CarrierTrackingNumber 
      FROM dbo.OrderDetails 
      WHERE ProductID = 776

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

    Примечание. Операция Sort (Сортировка) применяется в этом плане выполнения запроса для получения неповторяющегося результата.
  • Чтобы создать некластеризованный индекс в столбце Product ID таблицы OrderDetail, введите и выполните следующую инструкцию:
    CREATE INDEX NCLIX_OrderDetails_ProductID 
      ON dbo.OrderDetails(ProductID)
  • Введите предыдущую инструкцию SELECT и нажмите клавиатурную комбинацию (Ctrl+L), чтобы отобразить предполагаемый план выполнения.
    SELECT DISTINCT SalesOrderID, CarrierTrackingNumber 
      FROM dbo.OrderDetails 
      WHERE ProductID = 776

    Если вы задержите указатель мыши над оператором IndexSeek, то увидите, что SQL Server выполняет поиск по индексу (метод seek) в таблице NCLIX_OrderDetails_ProductID, чтобы извлечь указатели на нужные записи. Поскольку в этой таблице существует кластеризованный индекс, SQL Server извлекает список ключей кластеризации в качестве указателей. Этот список передается на вход оператора Nested Loops (Вложенные циклы), который является разновидностью оператора Join (Соединение) (соединения будут рассмотрены далее в этой лекции). Оператор Nested Loops использует поиск (метод seek ) по кластеризованному индексу, чтобы возвратить нужные строки данных, которые затем переходят к оператору Sort (Сортировка), чтобы исключить повторяющиеся значения в результате. Так SQL Server извлекает строки при помощи некластеризованного индекса, если существует кластеризованный индекс.

  • Теперь посмотрим, как SQL Server осуществляет доступ к данным, когда существует некластеризованный индекс в таблице, не имеющей кластеризованного индекса. Введите и выполните следующую инструкцию DROP INDEX, чтобы удалить из таблицы OrderDetails кластеризованный индекс.
    DROP INDEX OrderDetails.CLIDX_OrderDetails
  • Введите предыдущую инструкцию SELECT и нажмите клавиатурную комбинацию (Ctrl+L), чтобы отобразить предполагаемый план выполнения.
  • SELECT DISTINCT SalesOrderID, CarrierTrackingNumber 
      FROM dbo.OrderDetails 
      WHERE ProductID = 776

    Мы видим, что в этом случае SQL Server использует оператор RID Lookup, потому что указатели, которые SQL Server получил в результате поиска по индексу (метод seek), представляют собой указатели на физические строки данных, а не на ключи кластеризации. Оператор RID Lookup - это оператор, который используется в SQL Server для извлечения данных непосредственно со страницы.

  • Введите и выполните следующую инструкцию, чтобы снова создать кластеризованный индекс.
    CREATE UNIQUE CLUSTERED INDEX CLIDX_OrderDetails 
      ON dbo.OrderDetails(SalesOrderID,SalesOrderDetailID)
  • Использование покрывающих индексов

    Не всегда нужно, чтобы при использовании некластеризованных индексов SQL Server на втором этапе извлекал всю строку. Эта ситуация возникает, когда некластеризованный индекс включает все данные таблицы, которые нужно SQL Server для выполнения операции. Когда это происходит, мы называем индекс покрывающим, потому что этот индекс покрывает весь запрос. Покрывающие индексы могут существенно увеличить производительность запроса; в этом легко убедиться на двух планах запросов из предыдущего примера. В таких запросах стоимость операторов, которые извлекают нужные строки данных, составляет 97% всей стоимости запроса. Другими словами, запрос без этой операции будет выполнен в 32 раза быстрее. Давайте рассмотрим, как работают покрывающие индексы.

    Применяем покрывающие индексы

  • Запустите SQL Server Management Studio, Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Идея покрывающих индексов заключается в том, что они содержат все данные, необходимые для выполнения запросов. Если мы посмотрим на первый из следующих запросов, который уже использовался в предыдущем примере, то увидим, что SQL Server нужны столбцы SalesOrderID, CarrierTrackingNumber и ProductID.

    Некластеризованный индекс NCLIX_OrderDetails_ProductID, который мы создали ранее, включает столбец ProductID, поскольку он построен на этом столбце, а также столбец SalesOrderID, поскольку этот столбец является ключевым столбцом кластеризованного индекса. Поэтому SalesOrderID является указателем, который SQL Server использует в некластеризованном индексе. Следовательно, серверу SQL Server, чтобы получить CarrierTrackingNumber, нужно возвратить строки данных, выполнив поиск (метод seek) только по кластеризованному индексу. Во втором запросе столбца CarrierTrackingNumber нет в списке SELECT. Введите и выполните инструкцию, включив действительный план выполнения, чтобы увидеть разницу. Код этого примера имеется в файлах примеров под именем Using Covered Indexes.sql.

    SET STATISTICS IO ON
    -не покрывающий
    SELECT DISTINCT SalesOrderID, CarrierTrackingNumber
      FROM dbo.OrderDetails
      WHERE ProductID = 776 
    -покрывающий 
    SELECT DISTINCT SalesOrderID
      FROM dbo.OrderDetails
      WHERE ProductID = 776 
    SET STATISTICS IO OFF

    На рисунке, показанном ниже, видно, что серверу SQL Server для выполнения второго запроса нужно обратиться к кластеризованному индексу, потому что индекс покрывает запрос, если столбец CarrierTrackingNumber не выбран. Поскольку доступ к кластеризованному индексу для каждой строки очень дорого стоит, второй запрос составляет только 1% от общей стоимости пакета. Посмотрев на вкладку Messages (Сообщения), мы видим, что для того,. чтобы покрыть запрос, SQL Server нужно выполнить только 2 считывания страницы (вместо 709 считываний, необходимых для первого запроса).

    Вы убедились, что покрывающий индекс может дать большой выигрыш в скорости выполнения. Конечно, невозможно удалить столбцы из запроса, если они нужны в результате. Но по общему правилу следует извлекать только те столбцы, которые вам действительно нужны, чтобы сделать более вероятным применение покрывающего индекса. Не следует использовать в инструкции SELECT символ звездочки (*) только потому, что так проще составить запрос.

  • Предположим, что столбец CarrierTrackingNumber нужен в запросе, но из соображений производительности следует использовать покрывающий индекс. В SQL Server 2005 можно включить этот столбец в некластеризованный индекс. Включенные столбцы сохраняются в ключах индекса на уровне листовых вершин некластеризованного индекса, что устраняет необходимость извлекать их из кластеризованного индекса. Чтобы включить столбец CarrierTrackingNumber в некластеризованный индекс, введите и выполните следующую инструкцию для удаления (DROP) индекса и повторного создания ( CREATE ) индекса с включенным столбцом.
    DROP INDEX dbo.OrderDetails.NCLIX_OrderDetails_ProductID
    
    CREATE INDEX NCLIX_OrderDetails_ProductID 
      ON dbo.OrderDetails(ProductID) 
      INCLUDE (CarrierTrackingNumber)
  • Выполните первый неохваченный запрос, чтобы посмотреть, будет ли он охвачен теперь.
    SET STATISTICS IO ON
    SELECT DISTINCT SalesOrderID, CarrierTrackingNumber
      FROM dbo.OrderDetails
      WHERE ProductID = 776 
    SET STATISTICS IO OFF

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

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

    В таблице dbo.OrderDetails у нас есть вычисляемый столбец LineTotal, который представляет значение LineTotal для строки и вычисляется по следующей формуле:

    (isnull(([UnitPrice]*((1.0)-[UnitPriceDiscount]))*[OrderQty],(0.0)))

    При каждом доступе к этому столбцу SQL Server вычисляет значения на основе значений оригинальных строк, на которые ссылается формула. Этот процесс не представляет собой проблемы до тех пор, пока столбец LineTotal используется в предложении SELECT, но если использовать LineTotal в предикате поиска предложения WHERE или в агрегатных функциях, например, MAX или MIN, такая проблема может возникнуть. Если вычисляемые столбцы используются для поиска, SQL Server должен вычислять значения для каждой строки в таблице, и только потом искать в результатах нужные строки. Это очень неэффективный процесс, потому что здесь всегда требуется просмотр таблицы или полный просмотр кластеризованного индекса. Для этих видов запросов можно создать индексы в вычисляемых столбцах. Когда в вычисляемом столбце создан индекс, SQL Server вычисляет результат заранее и создает по нему индекс.

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

    Создаем и используем индексы в вычисляемых столбцах

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Представьте себе, что вы хотите возвратить все значения SalesOrderID s, для которых LineTotal имеет определенную величину. Введите следующий запрос и нажмите (Ctrl+L), чтобы отобразить предполагаемый план выполнения для случая, когда в вычисляемом столбце не существует опорного индекса. Как видите, SQL Server должен выполнить просмотр кластеризованного индекса, вычислить значения в столбце и отфильтровать эти значения до нужных строк. Код этого примера можно найти среди файлов примеров под именем IndexesOnComputedColumns.sql.
    SELECT SalesOrderID
    FROM OrderDetails
    WHERE LineTotal = 27893.619
  • Введите и выполните следующую инструкцию CREATE INDEX, чтобы создать индекс в вычисляемом столбце:
    CREATE NONCLUSTERED INDEX NCL_OrderDetail_LineTotal ON dbo.OrderDetails(LineTotal)
  • Выделите первый запрос и нажмите (Ctrl+L), чтобы отобразить новый план выполнения запроса с индексом в вычисляемом столбце. Как показано ниже, SQL Server теперь использует вновь созданный индекс для извлечения данных, что повышает скорость выполнения запроса по сравнению с предыдущим случаем.
    SELECT SalesOrderID
      FROM OrderDetails
      WHERE LineTotal = 27893.619
  • Закройте окно среды SQL Server Management Studio.
  • Индексы в столбцах XML

    SQL Server 2005 имеет собственный тип данных XML. Экземпляры XML в столбцах типа данных XML хранятся как большие двоичные объекты (BLOB) и могут иметь размер до 2 Гбайт на каждый экземпляр. Для запросов к XML-данным можно использовать язык XQuery, но такие запросы к столбцам с типом данных XML без индекса могут занимать много времени. Это особенно справедливо для больших экземпляров XML, поскольку SQL Server должен разбирать большие двоичные объекты, содержащие XML, в процессе выполнения рабочего цикла оценки запроса. Чтобы повысить производительность запросов к столбцам с типом данных XML, столбцы XML можно индексировать. XML-индексы делятся на две категории: первичные XML-индексы и вторичные XML-индексы.

    Создание и использование первичных XML-индексов

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

    CREATE PRIMARY XML INDEX index_name 
      ON <object> ( xml_column_name )

    Создаем первичный XML-индекс

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите и выполните следующие инструкции для создания таблицы, которая будет использоваться в следующих процедурах. Код этого примера можно найти среди файлов примеров под именем CreatingAndUsingPrimaryXMLIndexes.sql.
    CREATE TABLE dbo.Products( ProductID int NOT NULL, 
                 Name dbo.Name NOT NULL, CatalogDescription xml NULL,
    CONSTRAINT PK_ProductModel_ProductID PRIMARY KEY CLUSTERED ( ProductID ));
    INSERT INTO dbo.Products (ProductID,Name,CatalogDescription) 
    SELECT ProductModelID, Name, CatalogDescription 
      FROM Production.ProductModel;
  • Следующая инструкция CREATE INDEX используется для создания первичного индекса в столбце описания каталога таблицы Production.ProductModel.
    CREATE PRIMARY XML INDEX PRXML_Products_CatalogDesc 
      ON dbo.Products (CatalogDescription);
  • Еще одна инструкция использует метод XQuery для извлечения XML- данных только в том случае, если в XML-документе существует указанный путь. Введите и выполните эту инструкцию и обязательно включите план выполнения запроса.
    WITH XMLNAMESPACES
      ("http://schemas.microsoft.com/sqlserver/2004/07/adventure-works /ProductModelDescription'
        AS "PD")
    SELECT ProductID, CatalogDescription
      FROM dbo.Products
      WHERE CatalogDescription.exist
        ("/PD:ProductDescription/PD:Features") = 1
  • В следующем плане выполнения видно, что SQL Server использует индекс в XML-столбце, чтобы найти нужную запись и возвратить нужную строку данных при помощи оператора
  • Закройте окно среды SQL Server Management Studio.
  • Вторичные XML-индексы

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

  • Вторичный индекс Path типа данных XML.Вторичные XML-индексы, которые полезны при использовании метода .exist для определения существования указанного пути.
  • Вторичный индекс Value типа данных XML.Вторичный XML-индекс, который используется при выполнении запросов на основе значений, где полный путь неизвестен или в путь включены групповые символы.
  • Вторичный индекс Property типа данных XML.Вторичный XML-индекс, который используется для извлечения значений в том случае, если путь или значение неизвестны.

    Общая синтаксическая конструкция для создания вторичных индексов XML такова:

    CREATE XML INDEX index name 
      ON <object> ( xml column name ) 
      USING XML INDEX xml index name 
      FOR { VALUE | PATH | PROPERTY }
  • Создаем и используем вторичные XML-индексы

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите и выполните следующую инструкцию для создания вторичных индексов path, value и property в столбце XML CatalogDescription. Код этого примера можно найти среди файлов примеров под именем CreatingAndUsingSecondaryXMLindexes.sql.
    CREATE XML INDEX IXML Products CatalogDesc Path 
       ON dbo.Products (CatalogDescription) 
       USING XML INDEX PRXML Products CatalogDesc FOR PATH
    CREATE XML INDEX IXML_Products_CatalogDesc_Value 
      ON dbo.Products (CatalogDescription) 
      USING XML INDEX PRXML_Products_CatalogDesc FOR VALUE
    CREATE XML INDEX IXML_Products_CatalogDesc_Property 
      ON dbo.Products (CatalogDescription) 
      USING XML INDEX PRXML_Products_CatalogDesc FOR PROPERTY
  • Введите следующую инструкцию, которая использует метод XQuery, и запросите предполагаемый план выполнения, нажав комбинацию клавиш (Ctrl+L). Рассмотрим различные способы использования только что созданных индексов сервером SQL Server.
    WITH XMLNAMESPACES 
      ("http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/
        ProductModelDescription' AS "PD") SELECT *
      FROM dbo.Products
      WHERE CatalogDescription.exist
       ("/PD:ProductDescription/@ProductModelID[.="19"]') = 1;
    WITH XMLNAMESPACES 
      ("http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/
       ProductModelDescription' AS "PD") 
    SELECT *
      FROM dbo.Products
      WHERE CatalogDescription.exist ("//PD:*/@ProductModelID[.="19"]') = 1;
    WITH XMLNAMESPACES 
      ("http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/
        ProductModelDescription' AS "PD")
    SELECT CatalogDescription.value
        ("(/PD:ProductDescription/@ProductModelID)[1]",
         "int") as PID 
      FROM dbo.Products 
      WHERE CatalogDescription.exist 
         ("/PD:ProductDescription/@ProductModelID") = 1;
  • Закройте окно среды SQL Server Management Studio.
  • Индексы в представлениях

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

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

    В SQL Server 2005 Enterprise, Developer или Evaluation Edition индексированные представления могут ускорить выполнение запросов, которые не ссылаются на представления напрямую. Если обрабатываемый запрос включает, например, агрегат, и Оптимизатор запросов SQL Server обнаруживает индексированное представление, в которое этот агрегат уже включен, он запросит агрегат из индекса, а не станет вычислять его.

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

    Создаем индексированное представление

  • Создайте представление при помощи предложения SCHEMABINDING. Это представление должно удовлетворять нескольким требованиям. Например, оно может ссылаться только на базовые таблицы, которые существуют в одной базе данных. Все ссылочные функции должны быть детерминированными; функции наборов, производные таблицы и подзапросы не допустимы. Полный список требований можно найти в теме "Создание индексированных представлений" Электронной документации SQL Server 2005.
  • Создайте в этом представлении уникальный кластеризованный индекс. Уровень листовых вершин этого индекса состоит из полного результирующего набора представления.
  • При необходимости поверх кластеризованного индекса создайте не-кластеризованные индексы. Некластеризованные индексы можно создавать как обычно.
  • Создаем и используем индексированные представления

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите и выполните следующую инструкцию, чтобы создать представление, которое агрегирует итог LineTotal, сгруппированный по месяцам заказа. Код этого примера можно найти среди файлов примеров под именем CreatingAndUsingIndexedViews.sql.
    CREATE VIEW dbo.vOrderDetails
      WITH SCHEMABINDING AS
    SELECT DATEPART(yy,Orderdate) as Year,
        DATEPART(mm,Orderdate) as Month,
        SUM(LineTotal) as OrderTotal,
        COUNT_BIG(*) as LineCount 
      FROM dbo.Orders o INNER JOIN dbo.OrderDetails od
      ON o.SalesOrderID = od.SalesOrderID 
      GROUP BY DATEPART(yy,Orderdate),
      DATEPART(mm,Orderdate)
  • Введите и выполните следующую инструкцию SELECT. На вкладке Messages (Сообщения) видно, что для выполнения этой инструкции SQL Server требуется почти 1000 считываний страниц.
    SET STATISTICS IO ON 
    SELECT Year, Month, OrderTotal
      FROM dbo.OrderDetails
      ORDER BY Year, Month 
    SET STATISTICS IO OFF
  • Введите и выполните следующую инструкцию CREATE INDEX, чтобы создать уникальный кластеризованный индекс в представлении vOrderDetails.
    CREATE UNIQUE CLUSTERED INDEX CLIDX_vOrderDetails_Year_Month 
       ON dbo.vOrderDetails(Year,Month)
  • Выполните еще одну инструкцию SELECT. Обратите внимание на то, что SQL Server требуется только два считывания страниц, потому что результат уже вычислен и хранится в индексе.
    SET STATISTICS IO ON
    SELECT Year, Month, OrderTotal
       FROM dbo.OrderDetails
       ORDER BY Year, Month
    SET STATISTICS IO OFF
  • Если у вас установлена одна из следующих версий пакета: SQL Server 2005 Enterprise, Developer или Evaluation edtition - введите и выполните следующую инструкцию SELECT, которая не ссылается на пред ставление, и нажмите (Ctrl+L), чтобы запросить предполагаемый план выполнения, как показано ниже.
    SELECT DATEPART(yy,Orderdate) as Year,
          SUM(LineTotal) as YearTotal
       FROM dbo.Orders o INNER JOIN dbo.OrderDetails od
          ON o.SalesOrderID = od.SalesOrderID
       GROUP BY DATEPART(yy,Orderdate)
  • План выполнения предыдущего запроса показывает, что SQL Server использует кластеризованный индекс в представлении, чтобы извлечь данные, поскольку гораздо эффективнее создать агрегат YearTotal, вычислив сумму найденных в представлении агрегатов за месяц. Таким образом, мы видим, что при наличии индексированных представлений становятся возможным ускорить запрос, не изменяя код самого запроса.
  • Закройте окно среды SQL Server Management Studio.
  • Индексы, ускоряющие операции соединения

    Операторы соединения используются для соединения таблиц или промежуточных результатов. В SQL Server используется три типа операторов соединения.

  • Соединения вложенных циклов (Nested Loop Join) используют один ввод соединения в качестве внутренней входной таблицы, а другой ввод соединения в качестве внешней входной таблицы. Вложенные циклы однократно сканируют каждую строку ввода и ищут соответствующие строки во внешнем вводе. Если в столбцах условий соединения внешнего ввода существуют индексы, SQL Server может использовать поиск по индексу (метод seek) для отыскания строк во внешнем вводе. Если индексов не существует, то SQL Server приходится использовать операторы сканирования, чтобы найти во внешнем вводе совпадающие строки для каждой строки внутреннего ввода. Вложенные циклы всегда используются в тех случаях, когда внутренний ввод имеет всего несколько строк, поскольку в этом случае, это самая эффективная операция соединения.
  • Соединение слиянием (Merge Join) используется, когда вводы соединения сортируются по своим столбцам соединения. Выполняя операцию соединения слиянием, SQL Server сканирует однократно отсортированный ввод и выполняет слияние данных, подобно тому, как закрывают замок-молнию. Операция соединения слиянием очень эффективна, но сортировка данных должна быть выполнена заранее, а это означает, что в соединяемых столбцах должны существовать индексы. Если индексы не существуют, SQL Server может принять решение сначала выполнить сортировку ввода, но это не следует делать очень часто, поскольку сортировка данных - обычно неэффективный процесс.
  • Хэшированное соединение (Hash Join) используется для больших не-индексированных вводов без сортировки. Хэшированное соединение использует для соединения вводов операции хэширования в соединяемых столбцах. Чтобы вычислить и сохранить результат операции хэширования, SQL Server требуется больше времени и ресурсов процессора, чем для других операций соединения.
  • Изучаем операции соединения

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите и выполните следующую инструкцию, не забудьте включить действительный план выполнения запроса. Код этого примера можно найти в файлах примеров под именем Examining Join Operations.sql. План выполнения этой инструкции показан ниже. Мы видим, что SQL Server для того, чтобы извлечь строки внутреннего ввода (данные из таблицы OrderDetails ), использует поиск по индексу (метод seek ), а затем оператор Nested Loop Join, потому что во внешнем вводе есть только одна совпадающая строка (данные из таблицы Orders ). Совпадающие строки внешнего ввода возвращаются также с помощью поиска по индексу (метод seek ), поскольку существует совпадающий индекс. Для извлечения данных SQL Server требуется только пять считываний страниц, как указано на вкладке Messages (Сообщения).
    SET STATISTICS IO ON
    SELECT o.SalesOrderID, o.OrderDate, od.ProductID
       FROM dbo.Orders o INNER JOIN dbo.OrderDetails od
          ON o.SalesOrderID = od.SalesOrderID
       WHERE o.SalesOrderID = 43659
  • Измените запрос так, чтобы требовалось извлечь более одного значения SalesOrderID. В этом случае SQL Server выполнит соединение слиянием, поскольку в индексе есть отсортированные строки, а во внутреннем вводе много строк. Введите и выполните следующий запрос: Вы увидите, что для выполнения этого запроса SQL Server требуется 19 считываний страниц.
    SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
       FROM dbo.Orders o 
       INNER JOIN dbo.OrderDetails od 
          ON o.SalesOrderID = od.SalesOrderID 
       WHERE o.SalesOrderID BETWEEN 43659 AND 44000
  • Введите и выполните следующий пакет, который удаляет опорные индексы.
    DROP INDEX CLIDX_Orders_SalesOrderID ON dbo.Orders
     DROP INDEX CLIDX_OrderDetails ON dbo.OrderDetails
  • Снова выполните инструкцию SELECT, которую мы использовали ранее (она снова показана ниже) и изучите изменения планов выполнения запросов и операций ввода/вывода.
    SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
       FROM dbo.Orders o INNER JOIN dbo.OrderDetails od 
         ON o.SalesOrderID = od.SalesOrderID 
       WHERE o.SalesOrderID = 43659
    SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
       FROM dbo.Orders o INNER JOIN dbo.OrderDetails od 
         ON o.SalesOrderID = od.SalesOrderID 
       WHERE o.SalesOrderID BETWEEN 43659 AND 44000
  • План выполнения предыдущего запроса показывает, что SQL Server снова использует соединение вложенных циклов для первого запроса, потому что внутренний ввод небольшой, и хэшированное соединение для второго запроса, потому что данные ввода больше не сортируются. Поскольку опорных индексов не существует, SQL Server приходится сканировать в обоих случаях базовые таблицы полностью, что требует более 1000 считываний страниц для каждого запроса.

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

    Распределение данных и статистика

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

  • Запрос передается SQL Server.
  • Запускается Оптимизатор запросов SQL Server, который определяет, к каким данным необходимо выполнить доступ.
  • SQL Server выполняет поиск статистических данных в столбце, к которому происходит обращение.
  • Если статистика уже существует и не является устаревшей, SQL Server может продолжать выполнение запроса.
  • Если статистика не существует, SQL Server генерирует новые статистические данные.
  • Если статистика существует, но является устаревшей, SQL Server вычисляет новые статистические данные для этих данных.
  • Оптимизатор запросов SQL Server продолжает работу и генерирует план выполнения запроса.
  • Таково поведение по умолчанию, но для большинства баз данных существует лучший вариант. Можно воспользоваться инструкцией ALTER DATABASE, чтобы информировать SQL Server о том, что нужно обновлять данные статистики асинхронно; это означает, что программа не будет ждать новых статистических данных при генерации плана выполнения запроса. Безусловно, это означает, что сгенерированный план выполнения запроса может не быть оптимальным, поскольку при его создании были использованы устаревшие статистические данные. В особых случаях может быть желательным сгенерировать или обновить данные статистики вручную. Это можно сделать с помощью инструкций CREATE STATISTICS или UPDATE STATISTICS. Можно также отключить автоматическое создание и обновление индекса на уровне базы данных, выполнив инструкцию ALTER DATABASE. Все эти варианты следует использовать только в особых ситуациях, потому что поведение по умолчанию прекрасно подходит для большинства случаев. Дополнительную информацию об этих вариантах можно прочитать в официальном документе "Statistics Used by the Query Optimizer in Microsoft SQL Server 2005" (Статистические данные, используемые Оптимизатором запросов в Microsoft SQL Server 2005), который можно найти по ссылке http://www.microsoft.com/technet/prodtechnol/sql/ 2005/qrystats.mspx.

    Просматриваем статистику распределения данных

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Чтобы получить информацию о существующей статистике, можно выполнить запросы к представлениям sys.stats и sys.stats_columns.
  • Введите и выполните следующий запрос, чтобы узнать, какие статистические данные существуют для таблицы dbo:OrderDetails. Код этого примера можно найти среди файлов примеров под именем DataDistributionStatistics.sql.

    SELECT s.NAME, COL_NAME ( s.object_ID, sc.column_id) as CNAME 
      FROM sys.stats s INNER JOIN sys.stats_columns sc 
      ON s.stats_id = sc.stats_id 
      AND s.object_id = sc.object_id 
      WHERE s.object_id = OBJECT_ID('dbo.OrderDetails') 
      ORDER BY s.NAME;"

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

  • Чтобы получить статистическую информацию, можно использовать инструкцию DBCC SHOW_STATISTICS. Чтобы отобразить статистику для столбца LineTotal таблицы dbo.OrderDetails, введите и выполните следующую инструкцию:
  • DBCC SHOW_STATISTICS('dbo.OrderDetails', 'LineTotal')

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

  • RANGE_HI_KEY - значение верхней границы шага.
  • EQ_ROWS - количество строк, которое равно значению the RANGE_HI_KEY.
  • RANGE_ROWS - количество строк в диапазоне, не считая границ.
  • DISTINCT_RANGE_ROWS - количество неповторяющихся значений внутри диапазона.
  • Закройте окно среды SQL Server Management Studio.
  • Фрагментация индекса

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

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

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

    Если индексируемая таблица не является таблицей только для чтения, то ее индексы рано или поздно будут фрагментированы. Фрагмен-тированные индексы можно дефрагментировать для повышения скорости доступа к данным при помощи инструкции ALTER INDEX. Для дефрагментации предусмотрено два параметра:

  • REORGANIZE. Реорганизация индекса означает, что страницы уровня листовых вершин сортируются при помощи операции пузырьковой сортировки. REORGANIZE сортирует только страницы данных, а не записи на страницах; это означает, что параметр FILLFACTOR при реорганизации использовать нельзя.
  • REBUILD. Перестройка (rebuilding) индекса означает, что перестраивается весь индекс. Это требует больше времени, чем реорганизация индекса, но дает лучшие результаты. Можно указать параметр FILLFACTOR, чтобы страницы снова заполнялись до желаемой степени. Если параметр FILLFACTOR не указывается, то страницы уровня листовых вершин заполняются до предела. Параметр ONLINE также может указываться при перестройке индексов. Если этот параметр не указан, то перестройка индекса выполняется в автономном режиме, что означает блокировку таблицы на протяжении всего процесса. Перестройка в автономном режиме выполняется быстрее, чем в рабочем режиме, но из-за блокировки данных она не может использоваться в то время, когда необходим доступ к данным.
  • Обслуживаем индексы

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Чтобы получить информацию о фрагментации, используйте функцию наборов sys.dm_db_physical_stats. Чтобы извлечь список индексов с фрагментацией более 50%, введите и выполните следующую инструкцию. Код этого примера можно найти среди файлов примеров под именем IndexFragmentation.sql.
    SELECT object_name(i.object_id) as object_name ,i.name as IndexName
       ,ps.avg_fragmentation_in_percent
       ,avg_page_space_used_in_percent 
      FROM sys.dm_db_index_physical_stats(db_id(), NULL, NULL, NULL , 'DETAILED') as ps
      INNER JOIN sys.indexes as i 
         ON i.object_id = ps.object_id AND i.index_id = ps.index_id 
      WHERE ps.avg_fragmentation_in_percent > 50 
        AND ps.index_id > 0 ORDER BY 1
  • Чтобы выполнить перестройку индекса PK_Employee_EmployeeID в рабочем режиме, введите и выполните следующую инструкцию.
    ALTER INDEX PK_Employee_EmployeeID
       ON HumanResources.Employee
       REBUILD
     WITH (ONLINE = ON)
  • Закройте окно среды SQL Server Management Studio.
  • Примечание. Обычно процесс дефрагментации индекса лучше автоматизировать. Это можно сделать при помощи плана обслуживания (см. Электронную документацию по SQL Server 2005, тема "Как создать план обслуживания") или написав собственный сценарий, для которого можно настроить расписание при помощи службы Агент SQL Server.

    Настройка запросов с помощью Помощника по настройке ядра СУБД

    Создание правильных индексов для проекта базы данных - непростая задача. Здесь необходимо учесть множество факторов:

  • Модель данных базы данных
  • Объем и распределение данных в таблицах
  • Какие запросы к базе данных обычно выполняются
  • Как часто выполняются запросы
  • С какой частотой обновляются данные
  • Чтобы помочь пользователю в проектировании индексов, SQL Server предлагает инструмент, который называется Database Engine Tuning Advisor (Помощник по настройке ядра СУБД). Помощнику по настройке ядра СУБД необходим файл рабочей нагрузки, который может быть текстовым файлом, содержащим оптимизируемые инструкции, или файлом трассировки, который может быть сгенерирован при помощи компонента SQL Server Profiler. Затем Помощник по настройке ядра СУБД оптимизирует базу данных, которая использует оптимизатор запросов SQL Server и существующую базу данных, чтобы сгенерировать рекомендации по поводу изменений в физической структуре проекта (например, по поводу создания, изменения или удаления различных индексов).

    Используем Помощник по настройке ядра СУБД

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите следующие инструкции, которые мы будем оптимизировать с помощью Помощника по настройке ядра СУБД. Код этого примера можно найти среди файлов примеров под именем UsingDatabaseEngineTuningAdvisor.sql.
    USE AdventureWorks;
      SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
         FROM dbo.Orders o INNER JOIN dbo.OrderDetails od 
           ON o.SalesOrderID = od.SalesOrderID 
         WHERE o.SalesOrderID = 43659;
      SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
         FROM dbo.Orders o INNER JOIN dbo.OrderDetails od
           ON o.SalesOrderID = od.SalesOrderID
         WHERE o.SalesOrderID BETWEEN 43659 AND 44000;
  • Чтобы сохранить этот сценарий в качестве файла рабочей нагрузки, откройте меню File (Файл) и выберите команду Save As (Сохранить как). Сохраните файл под именем dta.sql.
  • В SQL Server Management Studio выберите из меню Tools (Сервис) команду Database Engine Tuning Advisor (Помощник по настройке ядра СУБД). Установите соединение с экземпляром SQL Server
  • Выберите файл, который мы сохранили в пункте 3 как файл рабочей нагрузки и выберите в качестве базы данных, подлежащей настройке,
  • Нажмите на панели инструментов кнопку Start Analysis (Начать анализ).
  • По завершении анализа откроется окно с рекомендациями, как показано на следующем рисунке:
  • Помощник по настройке ядра СУБД рекомендует создать два индекса. Чтобы сохранить сценарий для генерации индексов, выберите из меню Actions (Действия) команду Save Recommendations (Сохранить рекомендации).
  • Закройте окно Database Engine Tuning Advisor (Помощник по настройке ядра СУБД).
  • Как мы убедились, SQL Server в меру своих возможностей пытается оптимизировать два запроса. Это выгодно только в том случае, если эти запросы должны оптимизироваться без учета эффекта этой оптимизации на другие операции базы данных. Чтобы оптимизировать все индексы базы данных, неплохой идеей будет использование трассировки SQL Server Profiler, которую предоставляет Database Engine Tuning Advisor (Помощник по настройке ядра СУБД) с нормальной рабочей нагрузкой для всей базы данных. Благодаря этой информации Помощник по настройке ядра СУБД может оптимизировать запросы с другой рабочей нагрузкой в базе данных. После выполнения анализа рабочей нагрузки обязательно сохраните и просмотрите рекомендации.

    Заключение

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

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

    Из этой лекции вы узнали, как важно подобрать правильный тип индекса и как Помощник по настройке ядра СУБД может оказать помощь в разработке индекса.

    Краткий справочник по 2 лекции

    Чтобы Выполните следующие действия
    Просмотреть предполагаемый план выполнения запроса Нажмите (Ctrl+L) или выберите команду Display Estimated Execution Plan (Показать предполагаемый план выполнения) из меню Query (Запрос).
    Просмотреть действительный план выполнения запроса Нажмите (Ctrl+М) или выберите команду Include Actual Execution Plan (Включить действительный план выполнения) из меню Query (Запрос). Действительный план выполнения отображается на вкладке Execution Plan (План выполнения).
    Создать кластеризованный индекс
    CREATE UNIQUE CLUSTERED INDEX 
    <index_name> ON <table>(<column>)
    Создать некластеризованный индекс
    CREATE [ UNIQUE ] NONCLUSTERED INDEX index_name ON <object> ( column [ ASC | DESC ] [ ,...n ] )
    Создать первичный XML-индекс
    CREATE PRIMARY XML INDEX index_name ON <object> ( xml_column_name )
    Создать вторичный XML-индекс
    CREATE XML INDEX index_name 
    ON <object> ( xml_column_name ) USING XML INDEX xml_index_name 
    FOR { VALUE | PATH | PROPERTY }
    Просмотреть распределение данных Выполните запрос к представлениям sys.stats и sys.stats_columns. Для определенного столбца используйте инструкцию DBCC SHOW_STATISTICS(<table>, <column>)
    Получить информацию о фрагментации индексов Используйте функцию наборов sys.dm_db_physical_stats.
    Перестроить индекс и выполнить дефрагментацию
    ALTER INDEX <index> 
                         ON <table>.<column> 
                         REBUILD.
    Использовать Database Engine Tuning Advisor (Помощник по настройке ядра СУБД) В SQL Server Management Studio выберите из меню Tools (Сервис) команду Database Engine Tuning Advisor (Помощник по настройке ядра СУБД).
    Страницы:

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

    Планы запросов

    Когда сервер SQL Server выполняет запрос, сначала требуется определить наилучший способ выполнения. Для этого нужно рассчитать, как и в каком порядке обращаться к данным и соединять их, как и когда выполнять вычисления и агрегации и т. д. За это отвечает подсистема, которая называется Query Optimizer (Оптимизатор запроса). Оптимизатор запроса использует статистические данные о распределении данных, метаданные, относящиеся к объектам в базе данных, информацию индекса и другие факторы для вычисления нескольких возможных планов выполнения запроса. Для каждого из этих планов Оптимизатор запроса предполагает его стоимость на основе статистики по этим данным и выбирает план с минимальными затратами ресурсов на выполнение. Конечно, SQL Server не вычисляет всех возможных планов для каждого запроса, поскольку для некоторых запросов сами эти вычисления могут отнять больше времени, чем выполнение наименее эффективного из всех планов. Следовательно, SQL Server использует сложные алгоритмы, чтобы найти план выполнения с разумной стоимостью, близкой к минимально возможной. После того, как план выполнения сгенерирован, он хранится в буферном кэше (на что SQL Server выделяет большую часть своей виртуальной памяти). Затем план выполняется тем способом, который Оптимизатор запроса сообщает ядру базы данных (компоненту database engine).

    Примечание. Планы выполнения в буферном кэше могут быть повторно использованы при выполнении такого же или аналогичного запроса. Следовательно, планы выполнения хранятся в кэше максимально возможное время. Дополнительную информацию о кэшировании планов выполнения см. в официальном документе под названием: "Batch Compilation, Recompilation, and Plan Caching Issues in SQL Server 2005" (Проблемы компиляции и рекомпиляции пакетов, а также кэширования планов в SQL Server 2005) на странице http://www.microsoft.com/ technet/prodtechnol/sql/2005/recomp.mspx.

    Сможет ли Query Optimizer (Оптимизатор запросов) сгенерировать эффективный план для конкретного запроса, зависит от следующих аспектов:

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

    Примечание. Конечно, возможно, что неэффективно выполненный запрос выполнялся в соответствии с хорошим планом. В этих случаях дело не в оптимизации запроса. Скорее всего, проблема кроется совсем в другом, например, в проекте запроса, конфликте доступа к данным, операций ввода/вывода, памяти, использования ЦПУ, сетевых ресурсов и т. п. Чтобы получить дополнительную информацию по этим проблемам, рекомендуем ознакомиться с официальным документом "Troubleshooting Performance Problems in SQL Server 2005" (Поиск и решение проблем с производительностью в SQL Server 2005), который доступен по следующей ссылке: http://www.microsoft.com/ technet/prodtechnol/sql/2005/tsprfprb.mspx.

    Знакомимся с планами выполнения запросов

  • В меню Start (Пуск) выберите All Programs,. Microsoft SQL Server 2005, SQL Server Management Studio (Все программы, Microsoft SQL Server 2005, Среда SQL Server Management Studio). Нажмите кнопку New Query (Создать запрос), чтобы открыть окно нового запроса, и измените контекст выполнения на базу данных Adventure Works, выбрав ее из раскрывающегося списка Available Databases (Доступные базы данных).
  • Выполните следующую инструкцию SELECT. Код этого примера имеется в файлах примеров под именем Viewing Query Plans.sql.
    SELECT SalesOrderID, OrderQTY 
    FROM Sales.SalesOrderDetail 
    WHERE ProductID = 712 ORDER BY OrderQTY DESC
  • Чтобы вывести на экран план выполнения для этого запроса, нажмите комбинацию клавиш (Ctrl+L) или выберите из меню Query (Запрос) команду Display Estimated Execution Plan (Показать предполагаемый план выполнения). План выполнения показан на следующем рисунке.

    При генерировании предполагаемого плана запроса запрос на самом деле не выполняется. Он только оптимизируется Оптимизатором запроса. Эта особенность Оптимизатора запросов является преимуществом, когда приходится иметь дело с запросами, которые имеют продолжительные рабочие циклы, ведь для того, чтобы увидеть план выполнения запроса, нет необходимости выполнять сам запрос. Графическое представление плана выполнения запроса читается справа налево и сверху вниз. Каждый значок в плане представляет один оператор, а данные, изменяемые между этими операторами, обозначены стрелками. Толщина стрелок соответствует объему данных, которые передаются между операторами. Мы не будем углубляться в подробности и объяснять значение каждого оператора; расскажем только о тех из них, которые показаны в данном плане выполнения запроса.

  • SQL Server обращается к данным при помощи операции Clustered Index Scan (Просмотр кластеризованного индекса). Это сканирование представляет собой реальную операцию доступа к данным и подробно рассматривается далее.
  • Данные переходят к оператору Sort (Сортировка), который сортирует данные на основе предложения ORDER BY.
  • Данные пересылаются клиенту.
  • Мы рассмотрим самые важные операторы, которые использует SQL Server, когда будем изучать индексы и соединения. Полный список операторов можно найти в Электронной документации SQL Server 2005, тема "Пиктограммы графического представления плана выполнения".

    Стоимость в процентах под пиктограммой каждого оператора показывает процент от общей стоимости запроса, представленного на графической схеме. Это число поможет вам понять, какая операция использует при выполнении больше всего ресурсов. В нашем случае самой дорогостоящей операцией является Clustered Index Scan (Просмотр кластеризованного индекса), которая составляет 89% общей стоимости запроса.

  • Задержите указатель мыши над оператором

    В этом окне отображается подробная информация об операции. До сих пор мы знали только то, что SQL Server извлекает данные при помощи операции сканирования. Но в этом окне видно, что он выполняет операцию Clustered Index Scan (Просмотр кластеризованного индекса) (которая подробно рассматривается ниже) на кластеризованном индексе таблицы Sales.SalesOrderDetail, а также поиск ProductID 712. Эта информация находится в секции Predicates (Предикаты). Кроме того, показаны предполагаемая стоимость и предполагаемое количество строк, а также размер строки. В то время, как количество строк оценивается на основе статистики, которую SQL Server хранит для этой таблицы, значения стоимости вычисляются на основе статистики и значений эталонной системы. Следовательно, значения стоимости не следует использовать для того, чтобы рассчитать, сколько времени запрос будет выполняться на компьютере. Эти цифры могут использоваться только для выявления более дешевой или более дорогостоящей операции.

  • Эту информацию об операторах можно увидеть также в окне Properties (Свойства) в SQL Server Management Studio. Чтобы открыть окно Properties (Свойства), щелкните правой кнопкой мыши на значке оператора и выберите из контекстного меню команду Properties (Свойства).
  • Планы запросов можно также сохранить. Чтобы сохранить план запроса, щелкните в панели плана правой кнопкой мыши и выберите из контекстного меню команду Save Execution Plan As (Сохранить план выполнения как). План сохраняется в формате XML с расширением .sqlplan. Его можно открыть через SQL Server Management Studio. выбрав из меню File (Файл) команды Open, File (Открыть, Файл).
  • То, что вы видели до сих пор - это предполагаемый план выполнения запроса, но можно просмотреть и действительный план выполнения. Действительный план выполнения аналогичен предполагаемому плану выполнения, но включает также действительные (не предполагаемые) значения количества строк, количества перемоток и т. д. Чтобы включить в запрос действительный план выполнения, нажмите (Ctrl+M) или выберите из меню Query (Запрос) команду Include Actual Execution Plan (Включить действительный план выполнения). Затем нажмите F5 и выполните запрос. Результаты запроса отображаются как обычно, но вы увидите также план выполнения, который показан на вкладке Execution Plan (План выполнения).
  • Создание индексов для обеспечения более быстрого выполнения запросов

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

    Структуры кучи

    Файл данных в базе данных SQL Server делится на страницы по 8 Кбайт. Каждая страница содержит данные, индексы или другие типы данных, которые нужны SQL Server, чтобы манипулировать файлами данных. Однако большая часть страниц - это страницы данных или индекса. Страницы представляют собой единицы, которые SQL Server считывает и записывает из файлов данных. Каждая страница содержит данные или информацию индекса только для одного объекта базы данных. Поэтому на каждой странице данных вы найдете данные об одном объекте, а на каждой странице индекса - только информацию индекса. В SQL Server 2000 невозможно разбить строки данных на несколько страниц; это означает, что строка данных должна уместиться на странице, что ограничивает размер строки примерно 8060 байт (за исключением больших объектов данных). В SQL Server 2005 это ограничение больше не существует для типов данных переменной длины, таких, как nvarchar, varbinary, CLR и т. п. Благодаря типам данных переменной длины строки могут занимать несколько страниц, но все строки с типом данных фиксированной длины все же должны вписываться в одну страницу.

    Когда пользователь создает таблицу и вносит в нее данные, SQL Server осуществляет поиск неиспользуемых страниц, на которых можно сохранить данные. Чтобы отслеживать, какие страницы содержат данные для таблиц, SQL Server для каждой таблицы хранит еще одну или больше дополнительных страниц IAM, Index Allocation Map (Карта распределения индекса). Эти IAM-страницы указывают на страницы, на которых хранятся данные. Поскольку данные для этих таблиц хранятся на страницах без индекса, то есть, их объединяют только IAM-страницы, такие таблицы называются кучами. Чтобы обратиться к данным в куче, SQL Server должен прочитать IAM-страницу этой таблицы, а затем просмотреть страницы, на которые ссылается IAM-страница. Эта операция называется просмотром, или сканированием, таблицы. При просмотре таблицы данные считываются не по порядку. Если запрос выполняет поиск какой-либо одной определенной строки, то операции просмотра таблицы кучи приходится читать все строки в таблице только для того, чтобы найти нужную строку. Эта операция очень неэффективна.

    Изучаем структуры кучи

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • В следующем примере мы создадим две таблицы с именами dbo.Orders и dbo.OrderDetails. Для создания таблиц и заполнения их данными ведите и выполните следующие инструкции. Код для всего примера можно найти среди файлов примеров под именем Examining Heap Structures.sql.
    USE AdventureWorks; 
    GO
    CREATE TABLE dbo.Orders( SalesOrderID int NOT NULL, 
        OrderDate datetime NOT NULL, 
        ShipDate datetime NULL, 
        Status tinyint NOT NULL,
        PurchaseOrderNumber dbo.OrderNumber NULL, 
        CustomerID int NOT NULL, 
        ContactID int NOT NULL, 
        SalesPersonID int NULL 
        );
    CREATE TABLE dbo.OrderDetails( SalesOrderID int NOT NULL, 
        SalesOrderDetailID int NOT NULL, 
        CarrierTrackingNumber nvarchar(25), 
        OrderQty smallint NOT NULL, 
        ProductID int NOT NULL, 
        UnitPrice money NOT NULL, 
        UnitPriceDiscount money NOT NULL,
        LineTotal AS (isnull((UnitPrice*((1.0)-UnitPriceDiscount))*OrderQty,(0.0)))
        );
    INSERT INTO dbo.Orders
    SELECT SalesOrderID, OrderDate, ShipDate, Status, PurchaseOrderNumber,
        CustomerID, ContactID, SalesPersonID 
      FROM Sales.SalesOrderHeader;
    
    INSERT INTO dbo.OrderDetails(SalesOrderID, SalesOrderDetailID, 
                CarrierTrackingNumber, OrderQty,
                ProductID, UnitPrice, UnitPriceDiscount) 
    SELECT SalesOrderID, SalesOrderDetailID,CarrierTrackingNumber,OrderQty,
           ProductID, UnitPrice, UnitPriceDiscount 
      FROM Sales.SalesOrderDetail;
  • Мы создали две таблицы со структурой хранения "куча". Для выполнения запроса к таблице dbo.Orders введите инструкции, которые приводятся ниже. Включите действительный план выполнения, нажав (Ctrl+M) до начала выполнения или выбрав из меню Query (Запрос) команду Include Actual Execution Plan (Включить действительный план выполнения). Выполните запрос:
    SET STATISTICS IO ON; SELECT * FROM dbo.Orders
    SET STATISTICS IO OFF

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

  • Перейдите на вкладку Messages (Сообщения). Вы увидите примерно такое сообщение:

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

  • Перейдите на вкладку Execution Plan (План выполнения). В плане выполнения, показанном на следующем рисунке, мы видим, что SQL Server использовал операцию Table Scan (Просмотр таблицы) для доступа к данным, как единственный возможный вариант.
  • Теперь немного изменим запрос, чтобы он возвратил указанные строки.
    SET STATISTICS IO ON;
    SELECT * FROM dbo.Orders WHERE SalesOrderID =46699;
    SET STATISTICS IO OFF;
  • Посмотрим вывод в виде сообщения и графическое представление плана выполнения. Вы видите, что для этого запроса SQL Server все еще требуется 178 считываний страниц и использование операции Table Scan (Просмотр таблицы). Просмотр таблицы используется потому, что SQL Server не располагает индексом, и, следовательно, ему необходимо просмотреть все данные, чтобы найти нужную строку. p>Мы видим, что SQL Server использует операции просмотра таблиц для доступа к таблицам, не имеющим индекса. Эти просмотры вынуждают SQL Server просматривать все данные независимо от размера таблицы. Если таблица очень велика, то для ее просмотра может потребоваться много времени.
  • Индексы в таблицах

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

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

    Индексы в SQL Server встроены в древовидную структуру, которая называется сбалансированным деревом. Основная структура сбалансированного дерева показана ниже в примере. Как видите, нижний уровень называется уровнем листовых вершин. Уровень листовых вершин можно мыслить как оглавление книги. Он содержит по одной записи на каждую строку данных, причем записи сортируются по столбцу индекса. Чтобы ускорить поиск значений в индексе, дерево построено поверх него с использованием операций сравнения < (меньше чем) и > (больше чем). Число уровней индекса зависит от количества записей и размера ключа индекса. В реальных условиях страница индекса могла бы содержать намного больше значений, чем изображено в примере. Поскольку страница имеет размер 8 Кбайт, SQL Server может указать на странице индекса на тысячи страниц. Следовательно, индекс обычно не имеет много уровней, даже если таблица содержит миллионы строк. Этот факт способствует очень быстрому поиску определенных значений.

    Надписи:
    Root (Level 2) - Уровень корневой вершины (2 уровень)
    Intermediate (Level 1) - Уровень внутренних вершин (1 уровень)
    Leaf Level (Level 0) - Уровень листовых вершин (0 уровень)
    KEY Pointer - Ключ-указатель

    Как уже отмечалось ранее, в SQL Server используется два типа индексов: кластеризованный и некластеризованный. Оба типа индексов представляют собой сбалансированные деревья, но построены они по-разному. Давайте посмотрим, в чем заключается разница.

    Кластеризованные индексы

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

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

    CREATE [ UNIQUE ] CLUSTERED INDEX index name 
      ON <object> ( column [ ASC | DESC ] [ ,...n ] )

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

    Примечание. SQL Server создает уникальный индекс, если для таблицы определено ограничение первичного ключа, или уникальности. Если определен первичный ключ, то он создает кластеризованный индекс по умолчанию, если такой индекс до сих пор не существует в таблице. Этот вид индекса в SQL Server должен использоваться, если создание первичного, или уникального, ограничения, может быть определено в инструкциях CREATE или ALTER TABLE с ключевыми словами CLUSTERED или NONCLUSTERED.

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

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Для создания уникального кластеризованного индекса в таблице Orders ведите и выполните следующую инструкцию. Код этого примера имеется в файлах примеров под именем Creating And Using Clustered Indexes.sql.
    CREATE UNIQUE CLUSTERED INDEX CLIDX_Orders_SalesOrderID 
        ON dbo.Orders(SalesOrderID)
  • Теперь выполните эти же две инструкции SELECT, как делали это раньше, и изучите различия. Обязательно включите при выполнении запроса действительный план выполнения.
    SET STATISTICS IO ON;
    
    SELECT * FROM dbo.Orders;
    SELECT * FROM dbo.Orders 
      WHERE SalesOrderID =46699;
    
    SET STATISTICS IO OFF;
  • Перейдите на вкладку Execution Plan (План выполнения).

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

  • Просмотр индекса.Просмотр уровня листовых вершин индекса со считыванием данных таблицы. Поскольку первая инструкция SELECT не имеет предложения WHERE, серверу SQL Server известно, что нужно возвратить все данные, которые хранятся на уровне листовых вершин индекса.
  • Поиск по индексу.Операция, при которой SQL Server выполняет поиск определенного значения, проходя по ветвям индекса, начиная от корневой вершины индекса.
  • Эти две операции могут также соединяться, чтобы извлечь определенный диапазон данных. В этом виде операции частичного просмотра SQL Server пытается найти начало диапазона, а затем просматривает дерево до конца этого диапазона.

  • Перейдите на вкладку Messages (Сообщения), как показано ниже.

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

  • Введите следующую инструкцию SELECT, которая возвращает данные в отсортированной форме, и нажмите (Ctrl+L), чтобы получить предполагаемый план выполнения.
    SELECT * FROM dbo.Orders ORDER BY SalesOrderID;
    SELECT * FROM dbo.Orders ORDER BY OrderDate;

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

    Во втором запросе данные были отсортированы после получения. Следовательно, после операции просмотра кластеризованного индекса имела место операция Sort (Сортировка), которая отсортировала данные по столбцу OrderDate. Поскольку сортировка является очень дорогостоящей операцией, второй запрос генерирует 93% общей стоимости обоих запросов. Таким образом, имеет смысл определять кластеризованный индекс в столбцах, которые часто используются в качестве аргумента сортировки или группировки критериев в агрегате, поскольку агрегация данных требует, чтобы SQL Server сначала сортировал данные в соответствии с критериями группировки.

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

  • Создаем составной кластеризованный индекс

  • В SQL Server Management Studio введите и выполните следующие инструкции, чтобы создать составной кластеризованный индекс в таблице OrderDetails.
    CREATE UNIQUE CLUSTERED INDEX CLIDX_OrderDetails 
      ON dbo.OrderDetails(SalesOrderID,SalesOrderDetailID)
  • Теперь введите еще две инструкции SELECT. Первая будет выполнять поиск указанного значения SalesOrderID, а вторая -указанного значения SalesOrderDetailID. Оба столбца являются индексными столбцами нашего индекса CLIDX_OrderDetails. Нажмите (Ctrl+L), чтобы отобразить предполагаемый план выполнения.
    SELECT * FROM dbo.OrderDetails 
       WHERE SalesOrderID = 46999 
    SELECT * FROM dbo.OrderDetails 
       WHERE SalesOrderDetailID = 14147
  • Легко заметить, что в первом запросе, который выполняет поиск значения из первого столбца составного индекса, SQL Server для поиска строки использует поиск по индексам (метод seek). Во втором запросе он использует просмотр индекса, который является более дорогостоящей операцией. Просмотр индекса используется потому, что невозможно искать значение только во втором столбце составного индекса, ведь индекс изначально сортируется по первому столбцу. Следовательно, важно продумать порядок индексных столбцов в составном индексе. Запомните, что составной индекс следует использовать только тогда, когда поиск по дополнительным столбцам выполняется исключительно в сочетании с первым столбцом или если должна быть применена уникальность.

    Некластеризованные индексы

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

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

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

    CREATE [ UNIQUE ] NONCLUSTERED INDEX index name 
        ON <object> ( column [ ASC | DESC ] [ ,...n ] )
  • Создаем и применяем некластеризованные индексы

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите следующую инструкцию SELECT и нажмите клавиатурную комбинацию (Ctrl+L), чтобы отобразить предполагаемый план выполнения. Код этого примера имеется в файлах примеров под именем Creating And Using Nonclustered Indexes.sql.
    SELECT DISTINCT SalesOrderID, CarrierTrackingNumber 
      FROM dbo.OrderDetails 
      WHERE ProductID = 776

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

    Примечание. Операция Sort (Сортировка) применяется в этом плане выполнения запроса для получения неповторяющегося результата.
  • Чтобы создать некластеризованный индекс в столбце Product ID таблицы OrderDetail, введите и выполните следующую инструкцию:
    CREATE INDEX NCLIX_OrderDetails_ProductID 
      ON dbo.OrderDetails(ProductID)
  • Введите предыдущую инструкцию SELECT и нажмите клавиатурную комбинацию (Ctrl+L), чтобы отобразить предполагаемый план выполнения.
    SELECT DISTINCT SalesOrderID, CarrierTrackingNumber 
      FROM dbo.OrderDetails 
      WHERE ProductID = 776

    Если вы задержите указатель мыши над оператором IndexSeek, то увидите, что SQL Server выполняет поиск по индексу (метод seek) в таблице NCLIX_OrderDetails_ProductID, чтобы извлечь указатели на нужные записи. Поскольку в этой таблице существует кластеризованный индекс, SQL Server извлекает список ключей кластеризации в качестве указателей. Этот список передается на вход оператора Nested Loops (Вложенные циклы), который является разновидностью оператора Join (Соединение) (соединения будут рассмотрены далее в этой лекции). Оператор Nested Loops использует поиск (метод seek ) по кластеризованному индексу, чтобы возвратить нужные строки данных, которые затем переходят к оператору Sort (Сортировка), чтобы исключить повторяющиеся значения в результате. Так SQL Server извлекает строки при помощи некластеризованного индекса, если существует кластеризованный индекс.

  • Теперь посмотрим, как SQL Server осуществляет доступ к данным, когда существует некластеризованный индекс в таблице, не имеющей кластеризованного индекса. Введите и выполните следующую инструкцию DROP INDEX, чтобы удалить из таблицы OrderDetails кластеризованный индекс.
    DROP INDEX OrderDetails.CLIDX_OrderDetails
  • Введите предыдущую инструкцию SELECT и нажмите клавиатурную комбинацию (Ctrl+L), чтобы отобразить предполагаемый план выполнения.
  • SELECT DISTINCT SalesOrderID, CarrierTrackingNumber 
      FROM dbo.OrderDetails 
      WHERE ProductID = 776

    Мы видим, что в этом случае SQL Server использует оператор RID Lookup, потому что указатели, которые SQL Server получил в результате поиска по индексу (метод seek), представляют собой указатели на физические строки данных, а не на ключи кластеризации. Оператор RID Lookup - это оператор, который используется в SQL Server для извлечения данных непосредственно со страницы.

  • Введите и выполните следующую инструкцию, чтобы снова создать кластеризованный индекс.
    CREATE UNIQUE CLUSTERED INDEX CLIDX_OrderDetails 
      ON dbo.OrderDetails(SalesOrderID,SalesOrderDetailID)
  • Использование покрывающих индексов

    Не всегда нужно, чтобы при использовании некластеризованных индексов SQL Server на втором этапе извлекал всю строку. Эта ситуация возникает, когда некластеризованный индекс включает все данные таблицы, которые нужно SQL Server для выполнения операции. Когда это происходит, мы называем индекс покрывающим, потому что этот индекс покрывает весь запрос. Покрывающие индексы могут существенно увеличить производительность запроса; в этом легко убедиться на двух планах запросов из предыдущего примера. В таких запросах стоимость операторов, которые извлекают нужные строки данных, составляет 97% всей стоимости запроса. Другими словами, запрос без этой операции будет выполнен в 32 раза быстрее. Давайте рассмотрим, как работают покрывающие индексы.

    Применяем покрывающие индексы

  • Запустите SQL Server Management Studio, Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Идея покрывающих индексов заключается в том, что они содержат все данные, необходимые для выполнения запросов. Если мы посмотрим на первый из следующих запросов, который уже использовался в предыдущем примере, то увидим, что SQL Server нужны столбцы SalesOrderID, CarrierTrackingNumber и ProductID.

    Некластеризованный индекс NCLIX_OrderDetails_ProductID, который мы создали ранее, включает столбец ProductID, поскольку он построен на этом столбце, а также столбец SalesOrderID, поскольку этот столбец является ключевым столбцом кластеризованного индекса. Поэтому SalesOrderID является указателем, который SQL Server использует в некластеризованном индексе. Следовательно, серверу SQL Server, чтобы получить CarrierTrackingNumber, нужно возвратить строки данных, выполнив поиск (метод seek) только по кластеризованному индексу. Во втором запросе столбца CarrierTrackingNumber нет в списке SELECT. Введите и выполните инструкцию, включив действительный план выполнения, чтобы увидеть разницу. Код этого примера имеется в файлах примеров под именем Using Covered Indexes.sql.

    SET STATISTICS IO ON
    -не покрывающий
    SELECT DISTINCT SalesOrderID, CarrierTrackingNumber
      FROM dbo.OrderDetails
      WHERE ProductID = 776 
    -покрывающий 
    SELECT DISTINCT SalesOrderID
      FROM dbo.OrderDetails
      WHERE ProductID = 776 
    SET STATISTICS IO OFF

    На рисунке, показанном ниже, видно, что серверу SQL Server для выполнения второго запроса нужно обратиться к кластеризованному индексу, потому что индекс покрывает запрос, если столбец CarrierTrackingNumber не выбран. Поскольку доступ к кластеризованному индексу для каждой строки очень дорого стоит, второй запрос составляет только 1% от общей стоимости пакета. Посмотрев на вкладку Messages (Сообщения), мы видим, что для того,. чтобы покрыть запрос, SQL Server нужно выполнить только 2 считывания страницы (вместо 709 считываний, необходимых для первого запроса).

    Вы убедились, что покрывающий индекс может дать большой выигрыш в скорости выполнения. Конечно, невозможно удалить столбцы из запроса, если они нужны в результате. Но по общему правилу следует извлекать только те столбцы, которые вам действительно нужны, чтобы сделать более вероятным применение покрывающего индекса. Не следует использовать в инструкции SELECT символ звездочки (*) только потому, что так проще составить запрос.

  • Предположим, что столбец CarrierTrackingNumber нужен в запросе, но из соображений производительности следует использовать покрывающий индекс. В SQL Server 2005 можно включить этот столбец в некластеризованный индекс. Включенные столбцы сохраняются в ключах индекса на уровне листовых вершин некластеризованного индекса, что устраняет необходимость извлекать их из кластеризованного индекса. Чтобы включить столбец CarrierTrackingNumber в некластеризованный индекс, введите и выполните следующую инструкцию для удаления (DROP) индекса и повторного создания ( CREATE ) индекса с включенным столбцом.
    DROP INDEX dbo.OrderDetails.NCLIX_OrderDetails_ProductID
    
    CREATE INDEX NCLIX_OrderDetails_ProductID 
      ON dbo.OrderDetails(ProductID) 
      INCLUDE (CarrierTrackingNumber)
  • Выполните первый неохваченный запрос, чтобы посмотреть, будет ли он охвачен теперь.
    SET STATISTICS IO ON
    SELECT DISTINCT SalesOrderID, CarrierTrackingNumber
      FROM dbo.OrderDetails
      WHERE ProductID = 776 
    SET STATISTICS IO OFF

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

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

    В таблице dbo.OrderDetails у нас есть вычисляемый столбец LineTotal, который представляет значение LineTotal для строки и вычисляется по следующей формуле:

    (isnull(([UnitPrice]*((1.0)-[UnitPriceDiscount]))*[OrderQty],(0.0)))

    При каждом доступе к этому столбцу SQL Server вычисляет значения на основе значений оригинальных строк, на которые ссылается формула. Этот процесс не представляет собой проблемы до тех пор, пока столбец LineTotal используется в предложении SELECT, но если использовать LineTotal в предикате поиска предложения WHERE или в агрегатных функциях, например, MAX или MIN, такая проблема может возникнуть. Если вычисляемые столбцы используются для поиска, SQL Server должен вычислять значения для каждой строки в таблице, и только потом искать в результатах нужные строки. Это очень неэффективный процесс, потому что здесь всегда требуется просмотр таблицы или полный просмотр кластеризованного индекса. Для этих видов запросов можно создать индексы в вычисляемых столбцах. Когда в вычисляемом столбце создан индекс, SQL Server вычисляет результат заранее и создает по нему индекс.

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

    Создаем и используем индексы в вычисляемых столбцах

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Представьте себе, что вы хотите возвратить все значения SalesOrderID s, для которых LineTotal имеет определенную величину. Введите следующий запрос и нажмите (Ctrl+L), чтобы отобразить предполагаемый план выполнения для случая, когда в вычисляемом столбце не существует опорного индекса. Как видите, SQL Server должен выполнить просмотр кластеризованного индекса, вычислить значения в столбце и отфильтровать эти значения до нужных строк. Код этого примера можно найти среди файлов примеров под именем IndexesOnComputedColumns.sql.
    SELECT SalesOrderID
    FROM OrderDetails
    WHERE LineTotal = 27893.619
  • Введите и выполните следующую инструкцию CREATE INDEX, чтобы создать индекс в вычисляемом столбце:
    CREATE NONCLUSTERED INDEX NCL_OrderDetail_LineTotal ON dbo.OrderDetails(LineTotal)
  • Выделите первый запрос и нажмите (Ctrl+L), чтобы отобразить новый план выполнения запроса с индексом в вычисляемом столбце. Как показано ниже, SQL Server теперь использует вновь созданный индекс для извлечения данных, что повышает скорость выполнения запроса по сравнению с предыдущим случаем.
    SELECT SalesOrderID
      FROM OrderDetails
      WHERE LineTotal = 27893.619
  • Закройте окно среды SQL Server Management Studio.
  • Индексы в столбцах XML

    SQL Server 2005 имеет собственный тип данных XML. Экземпляры XML в столбцах типа данных XML хранятся как большие двоичные объекты (BLOB) и могут иметь размер до 2 Гбайт на каждый экземпляр. Для запросов к XML-данным можно использовать язык XQuery, но такие запросы к столбцам с типом данных XML без индекса могут занимать много времени. Это особенно справедливо для больших экземпляров XML, поскольку SQL Server должен разбирать большие двоичные объекты, содержащие XML, в процессе выполнения рабочего цикла оценки запроса. Чтобы повысить производительность запросов к столбцам с типом данных XML, столбцы XML можно индексировать. XML-индексы делятся на две категории: первичные XML-индексы и вторичные XML-индексы.

    Создание и использование первичных XML-индексов

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

    CREATE PRIMARY XML INDEX index_name 
      ON <object> ( xml_column_name )

    Создаем первичный XML-индекс

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите и выполните следующие инструкции для создания таблицы, которая будет использоваться в следующих процедурах. Код этого примера можно найти среди файлов примеров под именем CreatingAndUsingPrimaryXMLIndexes.sql.
    CREATE TABLE dbo.Products( ProductID int NOT NULL, 
                 Name dbo.Name NOT NULL, CatalogDescription xml NULL,
    CONSTRAINT PK_ProductModel_ProductID PRIMARY KEY CLUSTERED ( ProductID ));
    INSERT INTO dbo.Products (ProductID,Name,CatalogDescription) 
    SELECT ProductModelID, Name, CatalogDescription 
      FROM Production.ProductModel;
  • Следующая инструкция CREATE INDEX используется для создания первичного индекса в столбце описания каталога таблицы Production.ProductModel.
    CREATE PRIMARY XML INDEX PRXML_Products_CatalogDesc 
      ON dbo.Products (CatalogDescription);
  • Еще одна инструкция использует метод XQuery для извлечения XML- данных только в том случае, если в XML-документе существует указанный путь. Введите и выполните эту инструкцию и обязательно включите план выполнения запроса.
    WITH XMLNAMESPACES
      ("http://schemas.microsoft.com/sqlserver/2004/07/adventure-works /ProductModelDescription'
        AS "PD")
    SELECT ProductID, CatalogDescription
      FROM dbo.Products
      WHERE CatalogDescription.exist
        ("/PD:ProductDescription/PD:Features") = 1
  • В следующем плане выполнения видно, что SQL Server использует индекс в XML-столбце, чтобы найти нужную запись и возвратить нужную строку данных при помощи оператора
  • Закройте окно среды SQL Server Management Studio.
  • Вторичные XML-индексы

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

  • Вторичный индекс Path типа данных XML.Вторичные XML-индексы, которые полезны при использовании метода .exist для определения существования указанного пути.
  • Вторичный индекс Value типа данных XML.Вторичный XML-индекс, который используется при выполнении запросов на основе значений, где полный путь неизвестен или в путь включены групповые символы.
  • Вторичный индекс Property типа данных XML.Вторичный XML-индекс, который используется для извлечения значений в том случае, если путь или значение неизвестны.

    Общая синтаксическая конструкция для создания вторичных индексов XML такова:

    CREATE XML INDEX index name 
      ON <object> ( xml column name ) 
      USING XML INDEX xml index name 
      FOR { VALUE | PATH | PROPERTY }
  • Создаем и используем вторичные XML-индексы

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите и выполните следующую инструкцию для создания вторичных индексов path, value и property в столбце XML CatalogDescription. Код этого примера можно найти среди файлов примеров под именем CreatingAndUsingSecondaryXMLindexes.sql.
    CREATE XML INDEX IXML Products CatalogDesc Path 
       ON dbo.Products (CatalogDescription) 
       USING XML INDEX PRXML Products CatalogDesc FOR PATH
    CREATE XML INDEX IXML_Products_CatalogDesc_Value 
      ON dbo.Products (CatalogDescription) 
      USING XML INDEX PRXML_Products_CatalogDesc FOR VALUE
    CREATE XML INDEX IXML_Products_CatalogDesc_Property 
      ON dbo.Products (CatalogDescription) 
      USING XML INDEX PRXML_Products_CatalogDesc FOR PROPERTY
  • Введите следующую инструкцию, которая использует метод XQuery, и запросите предполагаемый план выполнения, нажав комбинацию клавиш (Ctrl+L). Рассмотрим различные способы использования только что созданных индексов сервером SQL Server.
    WITH XMLNAMESPACES 
      ("http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/
        ProductModelDescription' AS "PD") SELECT *
      FROM dbo.Products
      WHERE CatalogDescription.exist
       ("/PD:ProductDescription/@ProductModelID[.="19"]') = 1;
    WITH XMLNAMESPACES 
      ("http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/
       ProductModelDescription' AS "PD") 
    SELECT *
      FROM dbo.Products
      WHERE CatalogDescription.exist ("//PD:*/@ProductModelID[.="19"]') = 1;
    WITH XMLNAMESPACES 
      ("http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/
        ProductModelDescription' AS "PD")
    SELECT CatalogDescription.value
        ("(/PD:ProductDescription/@ProductModelID)[1]",
         "int") as PID 
      FROM dbo.Products 
      WHERE CatalogDescription.exist 
         ("/PD:ProductDescription/@ProductModelID") = 1;
  • Закройте окно среды SQL Server Management Studio.
  • Индексы в представлениях

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

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

    В SQL Server 2005 Enterprise, Developer или Evaluation Edition индексированные представления могут ускорить выполнение запросов, которые не ссылаются на представления напрямую. Если обрабатываемый запрос включает, например, агрегат, и Оптимизатор запросов SQL Server обнаруживает индексированное представление, в которое этот агрегат уже включен, он запросит агрегат из индекса, а не станет вычислять его.

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

    Создаем индексированное представление

  • Создайте представление при помощи предложения SCHEMABINDING. Это представление должно удовлетворять нескольким требованиям. Например, оно может ссылаться только на базовые таблицы, которые существуют в одной базе данных. Все ссылочные функции должны быть детерминированными; функции наборов, производные таблицы и подзапросы не допустимы. Полный список требований можно найти в теме "Создание индексированных представлений" Электронной документации SQL Server 2005.
  • Создайте в этом представлении уникальный кластеризованный индекс. Уровень листовых вершин этого индекса состоит из полного результирующего набора представления.
  • При необходимости поверх кластеризованного индекса создайте не-кластеризованные индексы. Некластеризованные индексы можно создавать как обычно.
  • Создаем и используем индексированные представления

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите и выполните следующую инструкцию, чтобы создать представление, которое агрегирует итог LineTotal, сгруппированный по месяцам заказа. Код этого примера можно найти среди файлов примеров под именем CreatingAndUsingIndexedViews.sql.
    CREATE VIEW dbo.vOrderDetails
      WITH SCHEMABINDING AS
    SELECT DATEPART(yy,Orderdate) as Year,
        DATEPART(mm,Orderdate) as Month,
        SUM(LineTotal) as OrderTotal,
        COUNT_BIG(*) as LineCount 
      FROM dbo.Orders o INNER JOIN dbo.OrderDetails od
      ON o.SalesOrderID = od.SalesOrderID 
      GROUP BY DATEPART(yy,Orderdate),
      DATEPART(mm,Orderdate)
  • Введите и выполните следующую инструкцию SELECT. На вкладке Messages (Сообщения) видно, что для выполнения этой инструкции SQL Server требуется почти 1000 считываний страниц.
    SET STATISTICS IO ON 
    SELECT Year, Month, OrderTotal
      FROM dbo.OrderDetails
      ORDER BY Year, Month 
    SET STATISTICS IO OFF
  • Введите и выполните следующую инструкцию CREATE INDEX, чтобы создать уникальный кластеризованный индекс в представлении vOrderDetails.
    CREATE UNIQUE CLUSTERED INDEX CLIDX_vOrderDetails_Year_Month 
       ON dbo.vOrderDetails(Year,Month)
  • Выполните еще одну инструкцию SELECT. Обратите внимание на то, что SQL Server требуется только два считывания страниц, потому что результат уже вычислен и хранится в индексе.
    SET STATISTICS IO ON
    SELECT Year, Month, OrderTotal
       FROM dbo.OrderDetails
       ORDER BY Year, Month
    SET STATISTICS IO OFF
  • Если у вас установлена одна из следующих версий пакета: SQL Server 2005 Enterprise, Developer или Evaluation edtition - введите и выполните следующую инструкцию SELECT, которая не ссылается на пред ставление, и нажмите (Ctrl+L), чтобы запросить предполагаемый план выполнения, как показано ниже.
    SELECT DATEPART(yy,Orderdate) as Year,
          SUM(LineTotal) as YearTotal
       FROM dbo.Orders o INNER JOIN dbo.OrderDetails od
          ON o.SalesOrderID = od.SalesOrderID
       GROUP BY DATEPART(yy,Orderdate)
  • План выполнения предыдущего запроса показывает, что SQL Server использует кластеризованный индекс в представлении, чтобы извлечь данные, поскольку гораздо эффективнее создать агрегат YearTotal, вычислив сумму найденных в представлении агрегатов за месяц. Таким образом, мы видим, что при наличии индексированных представлений становятся возможным ускорить запрос, не изменяя код самого запроса.
  • Закройте окно среды SQL Server Management Studio.
  • Индексы, ускоряющие операции соединения

    Операторы соединения используются для соединения таблиц или промежуточных результатов. В SQL Server используется три типа операторов соединения.

  • Соединения вложенных циклов (Nested Loop Join) используют один ввод соединения в качестве внутренней входной таблицы, а другой ввод соединения в качестве внешней входной таблицы. Вложенные циклы однократно сканируют каждую строку ввода и ищут соответствующие строки во внешнем вводе. Если в столбцах условий соединения внешнего ввода существуют индексы, SQL Server может использовать поиск по индексу (метод seek) для отыскания строк во внешнем вводе. Если индексов не существует, то SQL Server приходится использовать операторы сканирования, чтобы найти во внешнем вводе совпадающие строки для каждой строки внутреннего ввода. Вложенные циклы всегда используются в тех случаях, когда внутренний ввод имеет всего несколько строк, поскольку в этом случае, это самая эффективная операция соединения.
  • Соединение слиянием (Merge Join) используется, когда вводы соединения сортируются по своим столбцам соединения. Выполняя операцию соединения слиянием, SQL Server сканирует однократно отсортированный ввод и выполняет слияние данных, подобно тому, как закрывают замок-молнию. Операция соединения слиянием очень эффективна, но сортировка данных должна быть выполнена заранее, а это означает, что в соединяемых столбцах должны существовать индексы. Если индексы не существуют, SQL Server может принять решение сначала выполнить сортировку ввода, но это не следует делать очень часто, поскольку сортировка данных - обычно неэффективный процесс.
  • Хэшированное соединение (Hash Join) используется для больших не-индексированных вводов без сортировки. Хэшированное соединение использует для соединения вводов операции хэширования в соединяемых столбцах. Чтобы вычислить и сохранить результат операции хэширования, SQL Server требуется больше времени и ресурсов процессора, чем для других операций соединения.
  • Изучаем операции соединения

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите и выполните следующую инструкцию, не забудьте включить действительный план выполнения запроса. Код этого примера можно найти в файлах примеров под именем Examining Join Operations.sql. План выполнения этой инструкции показан ниже. Мы видим, что SQL Server для того, чтобы извлечь строки внутреннего ввода (данные из таблицы OrderDetails ), использует поиск по индексу (метод seek ), а затем оператор Nested Loop Join, потому что во внешнем вводе есть только одна совпадающая строка (данные из таблицы Orders ). Совпадающие строки внешнего ввода возвращаются также с помощью поиска по индексу (метод seek ), поскольку существует совпадающий индекс. Для извлечения данных SQL Server требуется только пять считываний страниц, как указано на вкладке Messages (Сообщения).
    SET STATISTICS IO ON
    SELECT o.SalesOrderID, o.OrderDate, od.ProductID
       FROM dbo.Orders o INNER JOIN dbo.OrderDetails od
          ON o.SalesOrderID = od.SalesOrderID
       WHERE o.SalesOrderID = 43659
  • Измените запрос так, чтобы требовалось извлечь более одного значения SalesOrderID. В этом случае SQL Server выполнит соединение слиянием, поскольку в индексе есть отсортированные строки, а во внутреннем вводе много строк. Введите и выполните следующий запрос: Вы увидите, что для выполнения этого запроса SQL Server требуется 19 считываний страниц.
    SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
       FROM dbo.Orders o 
       INNER JOIN dbo.OrderDetails od 
          ON o.SalesOrderID = od.SalesOrderID 
       WHERE o.SalesOrderID BETWEEN 43659 AND 44000
  • Введите и выполните следующий пакет, который удаляет опорные индексы.
    DROP INDEX CLIDX_Orders_SalesOrderID ON dbo.Orders
     DROP INDEX CLIDX_OrderDetails ON dbo.OrderDetails
  • Снова выполните инструкцию SELECT, которую мы использовали ранее (она снова показана ниже) и изучите изменения планов выполнения запросов и операций ввода/вывода.
    SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
       FROM dbo.Orders o INNER JOIN dbo.OrderDetails od 
         ON o.SalesOrderID = od.SalesOrderID 
       WHERE o.SalesOrderID = 43659
    SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
       FROM dbo.Orders o INNER JOIN dbo.OrderDetails od 
         ON o.SalesOrderID = od.SalesOrderID 
       WHERE o.SalesOrderID BETWEEN 43659 AND 44000
  • План выполнения предыдущего запроса показывает, что SQL Server снова использует соединение вложенных циклов для первого запроса, потому что внутренний ввод небольшой, и хэшированное соединение для второго запроса, потому что данные ввода больше не сортируются. Поскольку опорных индексов не существует, SQL Server приходится сканировать в обоих случаях базовые таблицы полностью, что требует более 1000 считываний страниц для каждого запроса.

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

    Распределение данных и статистика

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

  • Запрос передается SQL Server.
  • Запускается Оптимизатор запросов SQL Server, который определяет, к каким данным необходимо выполнить доступ.
  • SQL Server выполняет поиск статистических данных в столбце, к которому происходит обращение.
  • Если статистика уже существует и не является устаревшей, SQL Server может продолжать выполнение запроса.
  • Если статистика не существует, SQL Server генерирует новые статистические данные.
  • Если статистика существует, но является устаревшей, SQL Server вычисляет новые статистические данные для этих данных.
  • Оптимизатор запросов SQL Server продолжает работу и генерирует план выполнения запроса.
  • Таково поведение по умолчанию, но для большинства баз данных существует лучший вариант. Можно воспользоваться инструкцией ALTER DATABASE, чтобы информировать SQL Server о том, что нужно обновлять данные статистики асинхронно; это означает, что программа не будет ждать новых статистических данных при генерации плана выполнения запроса. Безусловно, это означает, что сгенерированный план выполнения запроса может не быть оптимальным, поскольку при его создании были использованы устаревшие статистические данные. В особых случаях может быть желательным сгенерировать или обновить данные статистики вручную. Это можно сделать с помощью инструкций CREATE STATISTICS или UPDATE STATISTICS. Можно также отключить автоматическое создание и обновление индекса на уровне базы данных, выполнив инструкцию ALTER DATABASE. Все эти варианты следует использовать только в особых ситуациях, потому что поведение по умолчанию прекрасно подходит для большинства случаев. Дополнительную информацию об этих вариантах можно прочитать в официальном документе "Statistics Used by the Query Optimizer in Microsoft SQL Server 2005" (Статистические данные, используемые Оптимизатором запросов в Microsoft SQL Server 2005), который можно найти по ссылке http://www.microsoft.com/technet/prodtechnol/sql/ 2005/qrystats.mspx.

    Просматриваем статистику распределения данных

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Чтобы получить информацию о существующей статистике, можно выполнить запросы к представлениям sys.stats и sys.stats_columns.
  • Введите и выполните следующий запрос, чтобы узнать, какие статистические данные существуют для таблицы dbo:OrderDetails. Код этого примера можно найти среди файлов примеров под именем DataDistributionStatistics.sql.

    SELECT s.NAME, COL_NAME ( s.object_ID, sc.column_id) as CNAME 
      FROM sys.stats s INNER JOIN sys.stats_columns sc 
      ON s.stats_id = sc.stats_id 
      AND s.object_id = sc.object_id 
      WHERE s.object_id = OBJECT_ID('dbo.OrderDetails') 
      ORDER BY s.NAME;"

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

  • Чтобы получить статистическую информацию, можно использовать инструкцию DBCC SHOW_STATISTICS. Чтобы отобразить статистику для столбца LineTotal таблицы dbo.OrderDetails, введите и выполните следующую инструкцию:
  • DBCC SHOW_STATISTICS('dbo.OrderDetails', 'LineTotal')

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

  • RANGE_HI_KEY - значение верхней границы шага.
  • EQ_ROWS - количество строк, которое равно значению the RANGE_HI_KEY.
  • RANGE_ROWS - количество строк в диапазоне, не считая границ.
  • DISTINCT_RANGE_ROWS - количество неповторяющихся значений внутри диапазона.
  • Закройте окно среды SQL Server Management Studio.
  • Фрагментация индекса

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

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

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

    Если индексируемая таблица не является таблицей только для чтения, то ее индексы рано или поздно будут фрагментированы. Фрагмен-тированные индексы можно дефрагментировать для повышения скорости доступа к данным при помощи инструкции ALTER INDEX. Для дефрагментации предусмотрено два параметра:

  • REORGANIZE. Реорганизация индекса означает, что страницы уровня листовых вершин сортируются при помощи операции пузырьковой сортировки. REORGANIZE сортирует только страницы данных, а не записи на страницах; это означает, что параметр FILLFACTOR при реорганизации использовать нельзя.
  • REBUILD. Перестройка (rebuilding) индекса означает, что перестраивается весь индекс. Это требует больше времени, чем реорганизация индекса, но дает лучшие результаты. Можно указать параметр FILLFACTOR, чтобы страницы снова заполнялись до желаемой степени. Если параметр FILLFACTOR не указывается, то страницы уровня листовых вершин заполняются до предела. Параметр ONLINE также может указываться при перестройке индексов. Если этот параметр не указан, то перестройка индекса выполняется в автономном режиме, что означает блокировку таблицы на протяжении всего процесса. Перестройка в автономном режиме выполняется быстрее, чем в рабочем режиме, но из-за блокировки данных она не может использоваться в то время, когда необходим доступ к данным.
  • Обслуживаем индексы

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Чтобы получить информацию о фрагментации, используйте функцию наборов sys.dm_db_physical_stats. Чтобы извлечь список индексов с фрагментацией более 50%, введите и выполните следующую инструкцию. Код этого примера можно найти среди файлов примеров под именем IndexFragmentation.sql.
    SELECT object_name(i.object_id) as object_name ,i.name as IndexName
       ,ps.avg_fragmentation_in_percent
       ,avg_page_space_used_in_percent 
      FROM sys.dm_db_index_physical_stats(db_id(), NULL, NULL, NULL , 'DETAILED') as ps
      INNER JOIN sys.indexes as i 
         ON i.object_id = ps.object_id AND i.index_id = ps.index_id 
      WHERE ps.avg_fragmentation_in_percent > 50 
        AND ps.index_id > 0 ORDER BY 1
  • Чтобы выполнить перестройку индекса PK_Employee_EmployeeID в рабочем режиме, введите и выполните следующую инструкцию.
    ALTER INDEX PK_Employee_EmployeeID
       ON HumanResources.Employee
       REBUILD
     WITH (ONLINE = ON)
  • Закройте окно среды SQL Server Management Studio.
  • Примечание. Обычно процесс дефрагментации индекса лучше автоматизировать. Это можно сделать при помощи плана обслуживания (см. Электронную документацию по SQL Server 2005, тема "Как создать план обслуживания") или написав собственный сценарий, для которого можно настроить расписание при помощи службы Агент SQL Server.

    Настройка запросов с помощью Помощника по настройке ядра СУБД

    Создание правильных индексов для проекта базы данных - непростая задача. Здесь необходимо учесть множество факторов:

  • Модель данных базы данных
  • Объем и распределение данных в таблицах
  • Какие запросы к базе данных обычно выполняются
  • Как часто выполняются запросы
  • С какой частотой обновляются данные
  • Чтобы помочь пользователю в проектировании индексов, SQL Server предлагает инструмент, который называется Database Engine Tuning Advisor (Помощник по настройке ядра СУБД). Помощнику по настройке ядра СУБД необходим файл рабочей нагрузки, который может быть текстовым файлом, содержащим оптимизируемые инструкции, или файлом трассировки, который может быть сгенерирован при помощи компонента SQL Server Profiler. Затем Помощник по настройке ядра СУБД оптимизирует базу данных, которая использует оптимизатор запросов SQL Server и существующую базу данных, чтобы сгенерировать рекомендации по поводу изменений в физической структуре проекта (например, по поводу создания, изменения или удаления различных индексов).

    Используем Помощник по настройке ядра СУБД

  • Запустите SQL Server Management Studio. Откройте окно New Query (Создать запрос) и измените контекст базы данных на Adventure Works.
  • Введите следующие инструкции, которые мы будем оптимизировать с помощью Помощника по настройке ядра СУБД. Код этого примера можно найти среди файлов примеров под именем UsingDatabaseEngineTuningAdvisor.sql.
    USE AdventureWorks;
      SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
         FROM dbo.Orders o INNER JOIN dbo.OrderDetails od 
           ON o.SalesOrderID = od.SalesOrderID 
         WHERE o.SalesOrderID = 43659;
      SELECT o.SalesOrderID, o.OrderDate, od.ProductID 
         FROM dbo.Orders o INNER JOIN dbo.OrderDetails od
           ON o.SalesOrderID = od.SalesOrderID
         WHERE o.SalesOrderID BETWEEN 43659 AND 44000;
  • Чтобы сохранить этот сценарий в качестве файла рабочей нагрузки, откройте меню File (Файл) и выберите команду Save As (Сохранить как). Сохраните файл под именем dta.sql.
  • В SQL Server Management Studio выберите из меню Tools (Сервис) команду Database Engine Tuning Advisor (Помощник по настройке ядра СУБД). Установите соединение с экземпляром SQL Server
  • Выберите файл, который мы сохранили в пункте 3 как файл рабочей нагрузки и выберите в качестве базы данных, подлежащей настройке,
  • Нажмите на панели инструментов кнопку Start Analysis (Начать анализ).
  • По завершении анализа откроется окно с рекомендациями, как показано на следующем рисунке:
  • Помощник по настройке ядра СУБД рекомендует создать два индекса. Чтобы сохранить сценарий для генерации индексов, выберите из меню Actions (Действия) команду Save Recommendations (Сохранить рекомендации).
  • Закройте окно Database Engine Tuning Advisor (Помощник по настройке ядра СУБД).
  • Как мы убедились, SQL Server в меру своих возможностей пытается оптимизировать два запроса. Это выгодно только в том случае, если эти запросы должны оптимизироваться без учета эффекта этой оптимизации на другие операции базы данных. Чтобы оптимизировать все индексы базы данных, неплохой идеей будет использование трассировки SQL Server Profiler, которую предоставляет Database Engine Tuning Advisor (Помощник по настройке ядра СУБД) с нормальной рабочей нагрузкой для всей базы данных. Благодаря этой информации Помощник по настройке ядра СУБД может оптимизировать запросы с другой рабочей нагрузкой в базе данных. После выполнения анализа рабочей нагрузки обязательно сохраните и просмотрите рекомендации.

    Заключение

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

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

    Из этой лекции вы узнали, как важно подобрать правильный тип индекса и как Помощник по настройке ядра СУБД может оказать помощь в разработке индекса.

    Краткий справочник по 2 лекции

    Чтобы Выполните следующие действия
    Просмотреть предполагаемый план выполнения запроса Нажмите (Ctrl+L) или выберите команду Display Estimated Execution Plan (Показать предполагаемый план выполнения) из меню Query (Запрос).
    Просмотреть действительный план выполнения запроса Нажмите (Ctrl+М) или выберите команду Include Actual Execution Plan (Включить действительный план выполнения) из меню Query (Запрос). Действительный план выполнения отображается на вкладке Execution Plan (План выполнения).
    Создать кластеризованный индекс
    CREATE UNIQUE CLUSTERED INDEX 
    <index_name> ON <table>(<column>)
    Создать некластеризованный индекс
    CREATE [ UNIQUE ] NONCLUSTERED INDEX index_name ON <object> ( column [ ASC | DESC ] [ ,...n ] )
    Создать первичный XML-индекс
    CREATE PRIMARY XML INDEX index_name ON <object> ( xml_column_name )
    Создать вторичный XML-индекс
    CREATE XML INDEX index_name 
    ON <object> ( xml_column_name ) USING XML INDEX xml_index_name 
    FOR { VALUE | PATH | PROPERTY }
    Просмотреть распределение данных Выполните запрос к представлениям sys.stats и sys.stats_columns. Для определенного столбца используйте инструкцию DBCC SHOW_STATISTICS(<table>, <column>)
    Получить информацию о фрагментации индексов Используйте функцию наборов sys.dm_db_physical_stats.
    Перестроить индекс и выполнить дефрагментацию
    ALTER INDEX <index> 
                         ON <table>.<column> 
                         REBUILD.
    Использовать Database Engine Tuning Advisor (Помощник по настройке ядра СУБД) В SQL Server Management Studio выберите из меню Tools (Сервис) команду Database Engine Tuning Advisor (Помощник по настройке ядра СУБД).
    Вернуться к учебному плану