|
|
|||||||||||||||||||||||||||||
|
Разбираемся с индексами на основе битовых картИсточник: CITFORUM Джонатан Льюис
Оглавление
Индексы на основе битовых карт - великое благо для некоторых видов приложений, но об их устройстве, использовании и побочных эффектах распространено очень много неверной информации. В этой статье описывается структура индексов на основе битовых карт и делается попытка разъяснить, почему возникли некоторые из наиболее типичных заблуждений, связанных с ними. Общеизвестно, что...Если провести простое исследование понимания разработчиками индексов на основе битовых карт, скорее всего, следующие комментарии окажутся наиболее типичными:
Третье утверждение, на самом деле, всего лишь следствие (возможно, не проверенное) второго. Причем, все три утверждения попадают в смутную зону между ложью и большим заблуждением. Конечно, в этих утверждениях есть и небольшая доля правды, - достаточная, чтобы объяснить их происхождение. Цель этой статьи - разобраться в структуре индексов на основе битовых карт, проверить истинность данных утверждений и попытаться выяснить преимущества индексов на основе битовых карт, а также какой ценой эти преимущества достигаются. Что такое индекс на основе битовой карты?Индексы создаются, чтобы сервер Oracle мог максимально эффективно находить запрошенные строки. Индексы на основе битовых карт - не исключение, однако стратегия, лежащая в основе этих индексов, очень отличается от стратегии, на которой базируются индексы на основе B*-дерева. Чтобы продемонстрировать это, мы начнем с изучения содержимого нескольких блоков. Рассмотрим SQL-сценарий на рис. 1.
Рисунок 1. Пример данных. Обратите внимание, что столбцы btree_col и bitmap_col заданы так, что содержат идентичные данные, - числа от 0 до 9, повторяющиеся циклически. В базе данных версии 9.2 с размером блока 8 Кбайт результирующая таблица займет 882 блока. Индекс на основе B*-дерева будет иметь 57 листовых блоков, а индекс на основе битовых карт - 10 листовых блоков. Фрагмент листового блока индекса на основе B*-дерева
Фрагмент листового блока индекса на основе битовых карт
Рисунок 2. Блоки данных, сброшенные в символьном виде. Понятно, что индекс на основе битовых карт несколько плотнее упакован, чем индекс на основе B*-дерева. Чтобы увидеть эту упаковку, можно сбросить блоки данных индекса в символьном виде с помощью команд типа:
Результаты представлены на рис. 2. Учтите, однако, что информация, полученная в результате сброса блока в символьном виде, может иногда приводить к неверным выводам, поскольку часть ее - производная от данных, и порядок следования тоже изменен по отношению к реальному для ясности. Блокируются ли таблицы при работе с индексами на основе битовых карт?Обратившись к рис. 2, можно увидеть, что запись индекса на основе B*-дерева состоит из набора флагов, байта блокировки, и (в данном случае) двух столбцов данных. Эти два столбца, на самом деле, - проиндексированное значение и идентификатор строки, причем, для каждой строки в таблице имеется запись этого вида в индексе. (Если бы индекс был уникальным, содержимое каждой записи было бы таким же, но расположение немного отличалось бы). В индексе на основе битовых карт каждая запись состоит из набора флагов, байта блокировки и (в данном случае) четырех столбцов данных. Эти четыре столбца, на самом деле, - проиндексированное значение, пара идентфикаторов строк и поток битов. Пара идентификаторов строк задает непрерывную часть таблицы, а поток битов говорит, какие строки в этом диапазоне идентификаторов строк содержат соответсвтующее значение. Посмотрите, однако, на размер потока битов, - длина столбца в представленном примере составляет 3521 байт, или около 27000 битов. Около 12% составляют накладные расходы на контрольные суммы и т.п., поэтому эта запись может покрыть порядка 24000 строк таблицы. Но на всю запись имеется только один байт блокировки, тем самым, одна блокировка будет влиять на 24000 строк таблицы. Вот откуда происходит сомнительное утверждение о блокировании всей таблицы, - если кажется, что изменение столбца ключа индекса на основе битовых карт вызывает блокирование всей таблицы, значит, вы экспериментировали со слишком маленькими таблицами. Одна блокировка битовой карты может затрагивать тысячи строк, что, несомненно, плохо, но вся таблица не блокируется. Последствия блокировок битовых картМы, однако, не должны останавливаться на этом этапе, поскольку результат очень легко поддается неверной интерпретации. Надо понять, какие действия приводят к установке критического байта блокировки, а также как в точности это влияет на тысячи соответствующих строк. Можно продолжить исследование с помощью намного меньшей тестовой таблицы (см. Рис. 3). Мы создадим таблицу, а затем будем выполнять различные изменения разных строк этой таблицы. Тестовые данные: Создание структур данных для примера
Изменение одной строки
Рисунок 3. Готовимcя к тестированию изменений. Обратите внимание, что изменен проиндексированный столбец лишь одной строки таблицы. Если сбросить в символьном виде блоки индекса и таблицы, окажется, что байт блокировки установлен для одной строки таблицы, но заблокированы две секции индекса на основе битовых карт. Это секция для близких строк с текущим значением 1 в проиндексированном столбце (секция, "откуда" убирается строка) и секция для близких строк со значением 2 (секция, "куда" переносится строка). (На самом деле, эти две секции битовых карт скопированы, и обе копии заблокированы). Теперь осталось разобраться, насколько "агрессивно" блокирует сервер Oracle в данном случае. Ответ может показаться несколько неожиданным для тех, кто мыслит категориями "индексы на основе битовых карт приводят к блокированию таблицы". Вполне можно сделать следующие изменения (каждое - в отдельном тесте). Изменить строку в секции "откуда", если это изменение не затрагивает столбец индекса на основе битовых карт.
Изменить строку в секции "куда", если это изменение не затрагивает столбец индекса на основе битовых карт.
Эти тесты показывают, что можно изменять строку, входящую в заблокированную секцию битовой карты. Конечно, конфликты блокирвок возможны, - например, ни один из следующих операторов не сможет изменить заблокированную строку таблицы, но их выполнение вызовет ожидание соответствующим сеансом снятия блокировки "TX" в режиме 4 (разделяемой блокировки)
Учтите, однако, что для возникновения проблемы необходимо выполнение двух условий. Во-первых, необходимо изменять проиндексированный столбец, а во-вторых, изменяемая строка должна покрываться ранее заблокированной секцией битовой карты, т.е. должна быть "сравнительно близко" от другой, изменяемой в настоящий момент, причем, конфликт может вызывать очень ограниченный набор значений (в нашем примере, 4). Помните, что вполне можно, в нашем случае, изменять столбец, по которому создан индекс на основе битовых карт, в близкой строке, если ни исходное, ни новое значения не равны 1 или 2. Например:
Итак, индексы на основе битовых карт НЕ вызывают блокирование таблицы. Если изменения не затрагивают проиндексированный столбец, наличие индексов на основе битовых карт не вызывает вообще никаких проблем. Даже если проиндексированные столбцы изменяются, можно создать набор не конфликтующих изменений. Проблемы с битовыми картамиКонечно, при использовании битовых карт возникает ряд проблем, выходящих за рамки конфликтов при изменении. Помните, что вставки и удаления из таблицы вызывают изменения всех ее индексов. С учетом большого количества строк, покрываемых одной записью индекса на основе битовых карт, при любом количестве одновременных вставок или удалений велика вероятность обращения к одним и тем же секциям индекса и, следовательно, массовых конфликтов доступа. Более того, даже последовательное выполнение операторов ЯМД, затрагивающих индексы на основе битовых карт, может куда существеннее сказаться на производительности, чем можно было предположить. Я уже подчеркивал, что простое изменение одной строки обычно приводит к копированию всей соответствующей секции битовой карты. Вернемся к Рис. 1 и вспомним, насколько большой может быть секция битовой карты. В этом примере она была размером 3500 байтов (в Oracle 9 максимальный размер составляет около половины блока). Можно обнаружить, что небольшое изменение данных может очень существенно повлиять на размер любого изменяемого вследствие этого индекса на основе битовых карт. Может, вам и повезет, но, в общем случае, стоит исходить из предположения, что даже последовательные пакетные изменения будут выполняться эффективнее, если удалить индексы на основе битовых карт перед их выполнением, а затем пересоздать. Столбцы с небольшим количеством значенийЧасто утверждается, что "индексы на основе битовых карт подходят для столбцов с небольшим количеством значений". Несколько точнее будет формулировка "с небольшим количеством различных значений". В любом случае, речь идет о столбцах, содержащих сравнительно мало различных значений. Это утверждение, действительно, достаточно верное, - если его соответствующим образом уточнить и разъяснить. К сожалению, многие, в результате, думают, что индекс на основе битовых карт чудесным образом настолько эффективен, что его можно использовать для доступа к большим частям таблицы способом, не считающимся целесообразным при использовании индекса на основе B*-дерева. Классическим примером применимости индекса на основе битовых карт является экстремальный случай столбца, представляющего пол. В этом столбце может быть всего два значения (или три, если включить требуемое стандартом ISO значение "n/a" - неизвестен). Мы будем чуть менее экстремальны и рассмотрим пример, основанный на странах, образующих Соединенное Королевство: Англия, Ирландия, Шотландия и Уэльс. Пусть используются блоки размером 8 Кбайт и строки (весьма типичным) размером 200 байтов, что дает 40 строк в блоке. Вставим в таблицу несколько миллионов строк, обеспечив равномерно случайное распределение по странам. Таким образом, в среднем в каждом блоке будет по 10 строк для каждой страны. Если использовать индекс на основе битовых карт для доступа ко всем строкам по Англии, придется (10 раз) последовательно прочитать каждый блок таблицы. Вне всякого сомнения, эффективнее будет выполнить полный просмотр таблицы, а не использовать такой индекс. На самом деле, даже если расширить данные так, чтобы они включали информацию по 40 странам, все равно вполне вероятно получить по одной строке в каждом блоке таблицы. Вероятно, когда данные разрастутся до глобального масштаба (скажем, охватят 640 стран, чтобы строка для данной страны встречалась в среднем лишь в каждом 16-ом блоке), может оказаться дешевле обращаться к ним по индексу на основе битовых карт, а не путем полного просмотра таблицы. Но столбец, имеющий 640 различных значений, вряд ли, на первый взгляд, попадает под определение "с небольшим количеством различных значений". Конечно, описательные выражения типа "небольшой", "маленький", "близкий к нулю" требуют определенного уточнения. Например, близко ли значение 10000 к нулю? Если сравнивать с десятью миллиардами, то да! Не используйте неопределенные выражения вроде "небольшое количество". В большинстве случаев, при выборе индексов на основе битовых карт необходимо учитывать только два фактора. Во-первых, количество различных блоков в таблице, в которых может находиться типичное значение индекса - это основной фактор выбора отдельного индекса. Изменение структуры индекса с B*-дерева на набор битовых карт не сделает этот индекс в это отношении лучше чудесным образом. Во-вторых, используемый оптимизатором Oracle механизм комбинирования нескольких битовых индексов делает их действительно полезными. Рассмотрим следующий пример, основанный на данных по примерно 64-миллионному населению Великобритании.
Каждому критерию отдельно соответствует очень много людей, но сколько кареглазых темноволосых женщин в возрасте 25 лет живет в Бирмингеме и работает в Лондоне? Где-то пару десятков.
Рисунок 4. Моделируем население Великобритании. Отдельный индекс (будь-то на основе B*-дерева или битовых карт) по любому из этих столбцов будет абсолютно бесполезен для выполнения такого запроса к таким данным в СУБД Oracle. Многостолбцовый индекс на основе B*-дерева по соответствующим шести столбцам может существенно помочь, пока нас не заинтересуют мужчины ростом 180 см. с бородой вместо темноволосых и кареглазых женщин. Можете поэкспериментировать (см. рис. 4), но понадобиться около 2,0 Гбайт места на диске и пару часов работы процессора с тактовой частотой порядка 500 МГц. Из-за нехватки места я построил модель поменьше, эмулирующую население порядка 36 миллионов. Время построения и размеры объектов для компьютера с тактовой частотой процессора 600 МГц, ОС Win2000 и сервером Oracle версии 9.2.0.1 представлены в следующей таблице.
Обратите, в частности, внимание на суммарный объем индексов, - 191 Мбайт. Всего лишь один многостолбцовый индекс по тем же шести столбцам (даже с максимальным сжатием) займет минимум 430 Мбайт, а сколько процессорного времени потребуется на его построение, - я вообще не знаю, да и немногие системы позволят построить этот индекс в памяти, поскольку для этого требуется установить параметру sort_area_size значение около 900 Мбайт. Итак, что же могут дать все эти битовые индексы? Рассмотрим запрос:
На сокращенном наборе данных, который я подготовил, при вводе подсказки, требующей выполнения полного просмотра таблицы, запрос выполнялся одну минуту и 20 секунд (вернув ответ 8). Конечно, при реальном наборе фактических данных таблица была бы существенно больше, как и время выполнения. При использовании полного шестистолбцового индекса объемом 430 Мбайт этот запрос выполнился бы, вероятно, за время, необходимое для выполнения около 10 физических чтений (один блок таблицы для каждой строки и пару дополнительных блоков индекса), - буквально за доли секунды. При наличии заданных ранее битовых индексов запрос выполнялся пять секунд. Большая часть этого времени ушла на сканирования диапазонов индексов, требующие физического считывания блоков индекса в память. Фактический план выполнения представлен на рис. 5.
Рисунок 5. План выполнения. В этом плане выполнения запроса следует отметить два интересных момента. Во-первых, сервер Oracle проигнорировал три "худших" (т.е., наименее избирательных) индекса. Во-вторых, хотя время выполнения - заметное, но размеры индексов настолько малы, что имеет смысл подумать об их размещении в достаточно большом KEEP-пуле, buffer_pool_keep (в Oracle 9 его размер задается параметром db_keep_cache_size), чтобы избежать физических чтений. Этот вариант вряд ли подходит для нескольких многостолбцовых индексов на основе B*-дерева, поддерживающих такие же запросы. Давайте подумаем о проигнорированных индексах, - в плане может использоваться практически любое количество индексов на основе битовых карт. Я видел случаи, когда сервер Oracle использовал более пяти таких индексов (это предел для способа доступа and_equal при использовании одностолбцовых индексов на основе B*-дерева). Три оставшихся индекса были проигнорированы не из-за какого-то искусственного ограничения. Стоимостной оптимизатор сравнил стоимость чтения каждого дополнительного индекса с достигаемой дополнительной точностью, и не выбрал их. Так что, индексы на основе битовых карт по классическому столбцу (пол) обычно игнорируются, несмотря на противоположные утверждения. (Удалите конструкцию work = 40 из запроса и убедитесь, что индекс по столбцу sex в этом случае используется). Конечно, такие индексы на основе битовых карт можно построить очень быстро, и места они займут немного, так что их можно создавать просто на всякий случай. РазмерыРазмер индексов и возможность максимальной буферизации необходимо, конечно, учитывать при определении стоимости, вот почему часто возникает вопрос, - насколько большим будет индекс на основе битовых карт? В представленном выше примере я попытался воссоздать наихудший случай, максимально затруднив серверу Oracle получение преимуществ от сжатия. В худшем случае, размер битовой карты в битах будет составлять:
Добавьте около 10 процентов на информацию контрольных сумм и накладные расходы, и поделите на 8, чтобы получить размер в байтах. К счастью, сервер Oracle позволяет предпринять ряд действий для сокращения размера индекса, наиболее действенным из которых является команда, сообщающая серверу, сколько в точности строк в блоке помещается в худшем случае для данной таблицы:
Однако помимо информирования сервера Oracle с помощью этой команды, очень существенное влияние на размер индекса имеет кластеризация данных. В примере я создал максимально разбросанные данные. Например, столбец town последовательно получает значения от 0 до 30. Если реструктурировать (по сути, отсортировать) данные так, что информация для всех городов с кодом 0 хранится вместе, а затем идется вся информация для городов с кодом 1, размер индекса можно сократить с 40 Мбайт практически до 7 Мбайт. Это существенное сокращение размера - еще один повод пересмотреть утверждение о "небольшом количестве различных значений". Потенциальная выгода от наличия индекса на основе битовых карт зависит от кластеризации данных (как и потенциальная выгода от наличия индекса на основе B*-дерева, конечно же). При принятии решения о создании индексов на основе битовых карт, не сбрасывайте со счетов столбцы с "большим" количеством различных значений. Если каждое значение встречается "много" раз, и строки для каждого значения в достаточной степени кластеризованы, то индекс на основе битовых карт очень даже подходит. В таблице со 100 миллионами строк столбец, имеющий 10000 различных значений, может оказаться отличным кандидатом для создания по нему индекса на основе битовых карт. ВыводОб индексах на основе битовых карт есть несколько существенно ошибочных представлений. Некоторые из них могут приводить к отказу от использования этих индексов, когда они могут существенно помочь. Другие приводят к созданию абсолютно бесполезных битовых индексов. К счастью, сделать серьезные ошибки при работе с индексами на основе битовых карт достаточно сложно. Но хорошее понимание того, как работает с ними сервер Oracle поможет получить от них максимальную пользу. Надо запомнить следующие ключевые факты:
Помните также, что оптимизатор улучшается в каждой версии Oracle. Граница между механизмами для индексов на основе B*-дерева и битовых карт существенно размывается за счет развития технологий сжатия индексов, просмотра индексов с пропуском (index skip scans), а также преобразования деревьев в битовые карты. Ссылки по теме
|
|