GUID – уникальный идентификатор. Его длина позволяет использовать GUID на разных машинах с весьма низкой вероятностью совпадения генерируемых GUIDs. Например, все документы (записи) баз данных Lotus Notes идентифицируются собственным GUID. Это позволяет решать проблемы при репликации документов. Некоторые сервера позволяют хранить GUID в его двоичном представлении, размером 16 байт. В IB нет возможности сохранить двоичное значение в строке, а blob для таких целей использовать будет неэффективно. Поэтому выход остается один – хранить GUID как строку.
Генерируемый функцией CoCreateGuid(ClassID) и StringFromCLSID(ClassID, P) GUID получается размером 38 символов, включая обрамляющие фигурные скобки и 4 символа '-'. Например, {C9EAF9E0-9B6B-11D3-9C10-008048E8D560}. Такое представление годится для Registry, но для хранения в качестве первичного ключа несколько избыточно.
Если исключить те самые фигурные скобки и тире, останется 32 символа. Однако IB не может эффективно хранить такие значения. Дело в том, что в строковом выражении GUID меняется в своей левой части. Т. е. младшие разряды строкового GUID находятся в левой части строки.
Как известно, IB производит упаковку ключей и записей при обновлении с помощью алгоритма, похожего на RLE. Это означает, что если создана запись со строкой "Иванов", а затем запись со строкой "Иванова", то ключ индекса по строковому полю для второй записи будет содержать число совпадений с предыдущим ключом, и несовпадающие символы. Буквально для нашего примера – 6'а'. При большом количестве таких упаковок, и при большом количестве совпадений, размер, требуемый для индекса на диске, должен быть существенно ниже.
Попробуем произвести тест. Создадим кроме функции CreateGUID функцию CreateRevGUID (см.
guid_udf.zip), где строка GUID будет перевернутой, т. е. младшие разряды окажутся в правой части строки.
Примечание. В Windows 2000, XP, 2003 и далее GUID генерируется функцией CoCreateGuid целиком случайно, а не последовательно, как это было на момент написания данного документа.
Создадим две таблицы у каждой по одному полю CHAR(32), и это поле сделаем первичным ключом. В одну таблицу вставим 100 тысяч записей с обычным CreateGUID, а во вторую – с CreateRevGUID.
После наполнения размер таблиц оказался одинаковым (это естественно), т. к. для записей при вставке упаковка не производится. (Размер страницы БД – 8К).
NGUIDS (130)
Primary pointer page: 135, Index root page: 136
Data pages: 953, data page slots: 953, average fill: 69%
Fill distribution:
0 - 19% = 0
20 - 39% = 1
40 - 59% = 0
60 - 79% = 952
80 - 99% = 0
А вот размер индексов оказался существенно разным. Для таблицы с CreateGUID:
Index RDB$PRIMARY3 (0)
Depth: 3, leaf buckets: 770, nodes: 100000
Average data length: 25.00, total dup: 0, max dup: 0
Fill distribution:
0 - 19% = 1
20 - 39% = 0
40 - 59% = 763
60 - 79% = 0
80 - 99% = 6
т. е. количество страниц индекса – 770, а для таблицы с CreateRevGUID:
Index RDB$PRIMARY2 (0)
Depth: 2, leaf buckets: 92, nodes: 100000
Average data length: 1.00, total dup: 0, max dup: 0
Fill distribution:
0 - 19% = 1
20 - 39% = 0
40 - 59% = 8
60 - 79% = 0
80 - 99% = 83
т. е. 92 страницы, что в
8 раз меньше!
После backup/restore эта разница оказывается в 2 раза меньшей (т. е. 4 раза), за счет того, что после restore индексные страницы оказываются заполненными на ~100% – индекс RDB$PRIMARY3 после restore занимал 384 страницы.
Разумеется, скорость поиска, выборки и сортировки по упакованным ключам выше, чем обычно, т. к. с диска требуется считывать меньшее количество страниц индекса.
Выводы:
- IB производит упаковку ключей индексов, даже если производится только вставка записей.
- Упаковываемые ключи плотнее заполняют пространство на страницах данных, чем неупаковываемые
- Для функций, аналогичных CreateGUID максимальной эффективности можно достичь, только если часто изменяемая часть строки находится в правой части.
Примечание. Еще большей степени упаковки можно достичь, используя функции
PackedGUID. Длина строки при использовании этих функций получается 26 байт. Однако результат функции NxPackGUID необходимо перевернуть так же, как и в CreateRevGUID (или производить конвертацию TGUID со старшего байта).
Примечание. Можно попробовать хранить GUID в естественном виде, т. е. длиной 16 байт. При этом строка должна быть создана как CHARACTER SET OCTETS. Этот набор символов позволяет хранить в строках произвольные символы, в том числе 0x. Однако клиентская часть также должна уметь сохранять строку целой, т. е. не обрезать ее до первого символа 0x, как это принято для строк C или строк pchar.
Например, в IBX или FreeIBComponents для этих целей компоненты, использующие стандартные Fields, не годятся – IBTable, IBDataSet, IBQuery. Они отрезают часть после 0x еще на этапе занесения значения параметров. А вот компонент IBSQL (FIBQuery) вполне подходит, и как записывает данные с 0x в такое поле, так и извлекает их обратно, поскольку работает с XSQLVAR в качестве полей запроса или его параметров.