Вы находитесь на странице: 1из 12

Лабораторная работа 7

Исследование индексных структур в SQL Server

Цель работы: исследовать размеры и поведение индексных структур та


неупорядоченных таблиц SQL Server.

Задание на лабораторную работу:

1. Исследование кучи.
2. Исследование кластеризированного индекса
3. Исследование некластеризированного индекса.
4. Создание индексов в БД (по индивидуальному варианту).
Ход работы

ИССЛЕДОВАНИЕ КУЧИ
Структуру SQL Server проще понять с помощью примеров. Следующий код
создает таблицу, организованную в виде кучи.
create table test_index(
id int not null,
pole1 char(36) not null,
pole2 char(216) not null
)

Если вы не создаете кластеризованный индекс явно или неявно с помощью


первичного ключа либо ограничения уникальности, таблица будет
организована как куча SQL Server не выделяет таблице никаких страниц при
ее создании. Он выделяет первую страницу, а также первую IAM-страницу,
когда выполняется первая встав ка строки в таблицу. Общую информацию о
таблицах и индексах можно найти в представлении каталога sys. indexes.
Следующий запрос извлекает основную информацию о таблице
dbo.TestStructure которая была создана в предыдущем коде.
select OBJECT_NAME(object_id) as table_name,
name as index_name, type, type_desc
from sys.indexes
where OBJECT_ID = OBJECT_ID(N'test_index')
Вот как выглядят результаты этого запроса.

Столбец type хранит значение 0 для куч, 1 для кластеризованных таблиц


(индексов и 2 для некластеризованных индексов. Можно узнать, сколько
страниц выделен под объект, из функции динамического управления
sys.dm_db_index_physical_stat или с помощью системной процедуры
dbo.sp_spaceused, как показано в следующем коде. Поскольку этот код
используется много раз в этом занятии, для простоты м] ссылаемся на него
как на "проверку выделения кучи".

Выходные данные этих двух команд выглядят следующим образом:

Вы видите, что таблица пустая, а пустая таблица не занимает места. Обратите


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

имеется достаточно места. Теперь вставьте первую строку.


insert into test_index
values(1, 'a','b')

Если вы выполните код проверки выделения кучи снова, результат будет сле-
дующим:
Таблица с одной строкой занимает одну страницу. Среднее использование
пространства страницы является низким, поскольку на странице только одна
строка. Результат процедуры dbo.sp_spaceused показывает, что у таблицы
есть две зарезервированные страницы: одна для данных и одна для первой
страницы IAM. Вы видите, что SQL Server выделяет только страницу, но не
экстент для этой таблицы. Теперь заполните страницу с помощью
следующего кода:
После выделения кучи запустите код снова. Вы получите следующий
результат:
declare @i as int=31

while @i<240
begin
set @i = @i+1;
insert into test_index
values(@i, 'a','b')
end;

По-прежнему выделена только одна страница; но эта страница не имеет


внутренней фрагментации, поскольку страница не может разместить
дополнительные строки. Попробуйте вставить дополнительные строки.
insert into test_index
values(31, 'a','b')
Код проверки выделения кучи возвращает следующие выходне данные:
Теперь вы видите, что выделена одна дополнительная страница из
смешанного экстента. Разумеется, внутренняя фрагментация возросла,
поскольку вторая страница почти пуста. Заполните 8 страниц с помощью
следующего кода:
declare @i as int=31
while @i<240
begin
set @i = @i+1;
insert into test_index
values(@i, 'a','b')
end;

Результаты выполнения кода проверки выделения кучи выглядят следующим


образом:

8 страниц заполнены. Что произойдет, если вы вставите еще одну строку?


Попробуйте это сделать с помощью следующего кода:
insert into test_index
values(241, 'a','b')
Результаты выполнения кода проверки выделения кучи выглядят следующим
образом:

Теперь вы видите, что хотя таблица занимает только 9 страниц, под нее
зарезервированы 16 страниц данных плюс первая страница IAM. Как
показывает результат процедуры dbo.sp_spaceused, SQL Server
зарезервировал 136 Кбайт под таблицу, что означает 17 страниц; 56 Кбайт
по-прежнему не используются. Неиспользованные 56 Кбайт пространства
означают, что 7 страниц из однородного экстента по- прежнему пусты.
Первые 8 страниц размещаются в смешанных экстентах. Поскольку таблица
уже больше 8 страниц, SQL Server выделяет однородные экстенты для
дополнительного пространства, которое необходимо.

ИССЛЕДОВАНИЕ КЛАСТЕРИЗИРОВАННОГО ИНДЕКСА


Следующий код усекает созданную и заполненную таблицу и реорганизует
эту таблицу в сбалансированное дерево с помощью столбца id как ключа
кластеризации.
truncate table test_index

create clustered index idx_cl_id on test_index(id)

Как видите, значение type изменилось на 1, и куча более не существует.


Когда вы создаете кластеризованный индекс, то фактически реорганизуете
таблицу. Теперь заполните 621 страницу этой таблицы, используя
уникальные значения для ключа кластеризации.
declare @i as int=0
while @i<18630
begin
set @i = @i+1;
insert into test_index
values(@i, 'a','b')
end;

Обратите внимание, если вы знаете, что значения должны быть


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

select index_type_desc, index_depth, index_level, page_count,


record_count, avg_page_space_used_in_percent,
avg_fragmentation_in_percent
from sys.dm_db_index_physical_stats
(db_id(N'test_index_db'), OBJECT_ID(N'dbo.test_index'), Null,
Null, 'Detailed')

exec dbo.sp_spaceused @objname = N'test_index', @updateusage = true;

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


будет следующим:

Вы видите, что индекс имеет только два уровня: конечный уровень и


корневую страницу. Корневая страница имеет 621 строку, которые
указывают на 621 конечную страницу. В этом случае внутренняя
фрагментация отсутствует. Теперь вставьте еще одну строку.
insert into test_index
values(18631, 'a','b')
Выполнив код проверки выделения кластеризованного индекса, вы получите
следующие выходные данные:

Теперь индекс имеет три уровня. Поскольку новая страница была выделена
на конечном уровне, начальная корневая страница не может уже ссылаться
на все конечные страницы. SQL Server добавил промежуточный уровень с
двумя страницами, указывающими на 622 конечных страницы, и новую
корневую страницу, указывающую на две страницы промежуточного уровня.
Чтобы продемонстрировать влияние uniquifier, следующий код усекает
таблицу и заполняет 423 страницы с помощью неуникальных значений
ключа кластеризации.
truncate table test_index
declare @i as int=0
while @i<8906
begin
set @i = @i+1;
insert into test_index
values(@i%100, 'a','b')
end;
Если вы выполните код проверки выделения кластеризованного индекса, то
получите следующий результат:

Заметьте, корневая страница может ссылаться только на 423 страницы


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

insert into test_index


values(8909%100, 'a','b')

Код проверки выделения кластеризованного индекса возвращает следующие


выходные данные:

Вы видите, что если значения ключа не уникальны, SQL Server должен


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

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


усекает таблицу dbo. Teststructure, удаляет существующий кластеризованный
индекс, создает новый с помощью столбца filierl в качестве ключа
кластеризации, а затем вставляет 9000 строк в таблицу с уникальными
последовательными значениями в ключе кластеризации.
truncate table test_index
drop index idx_cl_id on test_index
create clustered index idx_cl_pole1 on test_index(pole1)

declare @i as int=0
while @i<9000
begin
set @i = @i+1;
insert into test_index
values(@i, format(@i, '0000'),'b')
end;

Теперь проверьте фрагментацию. Наш код, который мы дальше будем на-


зывать кодом проверки фрагментации, проверяет внутреннюю фрагментацию
(столбец avg_page_space_used_in_percent) и внешнюю фрагментацию
(столбец avg fragmentation in percent).
Выходные данные кода проверки фрагментации в этом случае будут
такими:

Вы видите, что индекс имеет три уровня. На конечном уровне внутренняя


фрагментация отсутствует; кроме того, внешней фрагментации тоже почти
нет. Все страницы на конечном уровне заполнены, и физическая
последовательность почти такая же, как и логическая. Теперь выполните
усечение таблицы и заполните ее случайными значениями в столбце filierl.
Следующий код использует функцию NEWID() языка T-SQL, которая
генерирует идентификаторы GUID и сохраняет их в столбце fillerl.

truncate table test_index


declare @i as int=0
while @i<9000
begin
set @i = @i+1;
insert into test_index
values(@i, cast(newid() as char(36)),'b')
end;

Идентификаторы GUID, сгенерированные функцией NEWID(), почти


случайные. Если вы выполните код проверки фрагментации снова, то
получите следующий результат:

Вы видите, что страницы конечного уровня занимают только 68%


пространства, заполненного строками. Это потому, что SQL Server выполнил
разбиение нескольких страниц. Кроме того, внешняя фрагментация
составляет около 99%; почти ни одна страница не расположена в правильном
логическом порядке. Вы видите, что использование идентификаторов GUID
как ключей кластеризации может привести к созданию довольно
неэффективных индексов. Внешняя фрагментация в основном замедляет
просмотры, что не должно часто происходить в средах OLTP, которые очень
важны для хранилищ данных. Внутренняя фрагментация является проблемой
в обоих сценариях, потому что таблица намного больше, чем она должна
быть с последовательным ключом.
От фрагментации можно избавиться, если перестроить или реорганизовать
индекс. Реорганизация индекса — процесс более медленный, но менее
ресурсоемкий. В общем случае, следует выполнять реорганизацию индекса,
если внешняя фрагментация менее 30%, и перестраивать его, если она
больше 30%. Следующий код перестраивает индекс.
alter index idx_cl_pole1 on test_index rebuild

Если же вы предпочтете реорганизовать таблицу, вам нужно просто заменить


ключевое слово REBUILD ключевым словом REORGANIZE. Если вы выполните
код проверки фрагментации после перестройки индекса, то увидите в
выходных данных, что фрагментация практически исчезла.

ИССЛЕДОВАНИЕ НЕКЛАСТЕРНОГО ИНДЕКСА

За аналогією із завданнями 1,2 використовуючи наступний код дослідити


некластерні індекси та зробити відповідні пояснення:

--індекс на кучі
drop index idx_cl_pole1 on test_index

create nonclustered index idx_ncl_pole1 on test_index(pole1)


truncate table test_index

declare @i as int=0
while @i<24472
begin
set @i = @i+1;
insert into test_index
values(@i, format(@i, '0000'),'b')
end;

insert into test_index


values(24473, '000024473','b')

drop index idx_cl_id on test_index


create clustered index idx_cl_pid on test_index(id)
create nonclustered index idx_ncl_pole1 on test_index(pole1)
truncate table test_index

declare @i as int=0
while @i<28864
begin
set @i = @i+1;
insert into test_index
values(@i, format(@i, '0000'),'b')
end;

insert into test_index


values(28865, '000028865','b')

select * from test_index

select index_type_desc, index_depth, index_level, page_count,


record_count, avg_page_space_used_in_percent,
avg_fragmentation_in_percent
from sys.dm_db_index_physical_stats
(db_id(N'test1'), OBJECT_ID(N'dbo.test_index'), Null,
Null, 'Detailed')

alter index idx_ncl_pole1 on test_index rebuild


alter index idx_cl_pid on test_index rebuild
Створення індексів до власної БД
1. Вивести перелік вже створених в БД ІНДЕКСІВ
SELECT sysobjects.name AS Таблица, sysindexes.name AS Индекс, sysindexes.indid AS Номер
FROM sysobjects INNER JOIN
sysindexes ON sysobjects.id = sysindexes.id
WHERE (sysobjects.xtype = 'U') AND (sysindexes.indid > 0)
ORDER BY sysobjects.name, sysindexes.indid

2. Створіть некластерізованний індекс для стовпця


date_rozm таблиці zakaz, із заповненням простору кожної
сторінки листя індексу на 70%.

create nonclustered index ix_date_rozm on zakaz(date_rozm)


with fillfactor = 70

select OBJECT_NAME(object_id) as table_name,


name as index_name, type, type_desc
from sys.indexes
where OBJECT_ID = OBJECT_ID(N'zakaz')

3. Створить однозначний складовою індекс для стовпців Nazva і City таблиці


клієнт.
drop index ix_klient_nazva_city on klient
create nonclustered index ix_klient_nazva_city on klient(city, nazva)

4. Чи буде якась різниця, якщо змінити порядок стовпців зразків в складеному


індексі? (перевірити експерементально)

select index_type_desc, page_count,


record_count, avg_page_space_used_in_percent, avg_fragment_size_in_pages
from sys.dm_db_index_physical_stats
(db_id(N'torg_firm'), OBJECT_ID(N'klient'), Null,
Null, 'Detailed')

5. Знищити індекс, який був створений для первинного ключа таблиці


sotrudnik?
alter table zakaz drop constraint FK__zakaz__id_sotrud__2D27B809
alter table sotrudnik drop constraint PK__sotrudni__668829F13DE3C28F

6. Створити індекси які підвищать продуктивність запитів. Перевірити за


планом вионання запитів
SELECT id_tovar, Nazva, price FROM tovar WHERE Nazva = 'Moloko'
SELECT id_tovar, Nazva, price FROM tovar WHERE Nazma = 'Молоко' AND Price =
10
SELECT nazva FROM zakaz_tovar, tovar WHERE tovar.id_tovar = tovar.id_tovar
SELECT nazva FROM zakaz_tovar, tovar WHERE tovar.id_tovar = tovar.id_tovar
AND Price >10

Звіт містить:
1. Лістингу коду завдання 1,2
2. Лістинг коду завдання 3 із коментарями-порівнянням параметрів
некластерного індексу із кучею та кластеризованим.
3. Лістинг коду завдання 4 із обґрунтуванням вибору стовпців обраних для
індексації.