Размещение таблиц

Размер записи таблицы

Размер записи - это суммарный размер всех полей (колонок) записи. Размер колонки в байтах полностью определяется типом данных в этой колонке:

Тип данныхРазмер в байтах
integer, serial4
smallint2
float8
smallfloat, real4
char, ncharдлина, указанная в определении типа
varchar, nvarcharдлина, указанная в определении типа + 1
lvarcharдлина, указанная в определении типа + 3
byte, text56; само значение хранится отдельно от записи
date4
boolean2
decimal, moneyот 1 до 17: 2 цифры на байт и полбайта на знак
datetime, intervalот 2 до 12: (число цифр символьного представления) / 2 + 1
int8, serial810 или 8 - в зависимости от версии СУБД

Если считать не хочется, можно поступить иначе: создать пустую таблицу с нужной структурой, а потом выполнить запрос

	SELECT rowsize FROM systables WHERE tabname = 'имя_таблицы'

Размещение записей в страницах

Записи таблицы размещаются в страницах дисковой памяти. Размер страницы зависит от версии СУБД и от операционной среды, в которой работает СУБД.

В старых версиях Informix размер страницы был фиксированным (в системах Windows - 4 килобайта, в системах Unix (Linux, Solaris) - 2 килобайта). В последних версиях Informix - начиная с 11-й - администратор имеет возможность задать использование страниц других размеров - до 64 килобайт.

Базовые правила следующие:

Кроме всего этого, к каждой записи добавляется 4 байта служебной информации. Следовательно, для размещения записи длиной 14 байт реально понадобится 18 байт.

Таким образом, страница может содержать больше одной записи, только если размер записи не больше, чем половина места на пустой странице минус 4 ((2048 - 28) / 2 - 4 = 1006 байт в случае 2-килобайтных страниц).

Описанный алгоритм применим без поправок в том случае, если все записи таблицы - одинакового размера. Если же таблица содержит поля переменного размера (VARCHAR, NVARCHAR, LVARCHAR), все выглядит чуть-чуть иначе.

Когда СУБД определяет, можно ли разместить очередную запись в уже используемой странице или нужно выделить новую страницу, проверяется наличие свободного места на странице, исходя из максимального размера записи (то есть размера, записанного в systables.rowsize). Однако реальный размер записи может оказаться существенно меньше максимального, и в результате на странице останется несколько больше свободного места, чем ожидалось изначально. В итоге в странице может поместиться больше записей, чем прогнозировалось исходя из размера записей. Тем не менее, если свободного места на странице меньше, чем максимальный размер записи, новая запись в любом случае будет помещена на новую страницу, даже если фактический размер таков, что она могла бы поместиться на уже используемую страницу. Сделано это для того, чтобы минимизировать число операций переноса записей со страницы на страницу при их изменении (запись может впоследствии удлиниться).

В любом случае на странице можно разместить не более 255 записей - независимо ни от размера записи, ни от размера страницы.

Выделение памяти для таблиц - экстенты

Память для хранения таблиц данных выделяется экстентами. Экстент - это группа последовательных страниц данных - причем непрерывная группа.

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

Минимальный размер экстента в Informix - четыре страницы.

Для таблицы можно задать два размера экстентов:

Пример: (размер страницы 2 кбайт): таблица

	create table tab1 (
		. . . . . . .
	) extent size 1000 next size 200;

Для первого экстента будет выделено 1000 килобайт, или 500 страниц. Экстенты, начиная со второго, будут иметь размер 200 килобайт (100 страниц).

Если размер экстентов оказался слишком маленьким, по мере заполнения таблица будет занимать множество мелких экстентов. В таких случаях СУБД пытается хотя бы частично корректировать эту ситуацию и делает это следующим образом. Если для таблицы выделено 15 экстентов и нужен 16-й, менеджер памяти УДВАИВАЕТ значение NEXT SIZE для таблицы. В нашем примере экстенты со 2-го по 15-й будут по 200 килобайт, с 16-го по 31-й - по 400 килобайт, с 32-го по 47-й - 800 килобайт и так далее.

Выбор размера экстента

Выбрать оптимальный размер экстента не очень просто.

Если выбрать использование маленьких экстентов, это позволит сэкономить дисковую память. Но при этом сильно увеличивается число самих экстентов. Однако слишком уж увеличивать их число нельзя по двум причинам:

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

Оптимальным считается случай, когда число экстентов не превышает 2-3. С одной стороны, при этом нет большого перерасхода памяти, с другой - степень фрагментации данных невелика.

Если известны размер записи таблицы и число записей, объем памяти для размещения данных можно рассчитать так:

	int	pagesize,	// размер страницы в байтах
		recordsize,	// размер записи в байтах
		recordcount,	// количество записей таблицы
		pageuse,	// доступное место на странице в байтах
		data_pages,	// количество страниц для данных
		homepages,	// количество корневых страниц
		fullrempages,	// количество полных страниц расширения
		partrempages,	// количество неполных страниц расширения
		remsize,	// размер промежуточного остатка записи
		partremsize	// размер финального остатка записи
		;
	double	partratio;	// коэффициент заполнения страницы финальным остатком

	pageuse = pagesize - 28;

	if (recordsize <= pageuse) {
		data_pages = recordcount / TRUNC(pageuse / (recordsize + 4));

	} else {
		homepages = recordcount;
		remsize = recordsize - (pageuse + 8);

		if (remsize < (pageuse - 4)) {
			fullrempages = 0;
		} else {
			fullrempages = recordcount * TRUNC(remsize / (pageuse - 8));
		}

		partremsize = recordsize % (pageuse - 8) + 4;
		partratio = ((double)partremsize) / ((double)pageuse);

		if (partratio < 0.1) {
			partrempages = recordcount / (TRUNC((pageuse / 10) / remsize) + 1)
		} else if (partratio < 0.33) {
			partrempages = recordcount / (TRUNC((pageuse / 3) / remsize) + 1)
		} else {
			partrempages = recordcount;
		}

		data_pages =  homepages +  fullrempages +  partrempages;
	}
	printf("Data pages %1d\n", data_pages);
	printf("Extent size %1d\n", data_pages * (pagesize / 1024));

Приведенный алгоритм взят из документации Informix 10 и ориентирован на таблицы с записями фиксированного размера.

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

Рекомендации

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

Для временных (рабочих) таблиц рекомендуется экономить место и выбирать минимальные размеры экстентов.

Для постоянных таблиц, которые со временем будут только расти, рекомендуется выбирать размеры таким образом, чтобы в течение года образовалось не более 2-3 экстентов.

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

Дефрагментация

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

Методы дефрагментации зависят от версии СУБД и от размеров дефрагментируемой таблицы.

1. Изменение размера дополнительного экстента

Этот метод доступен начиная с версии 7. Вначале меняем характеристики таблицы

	ALTER TABLE имя_таблицы MODIFY NEXT SIZE новый_размер_экстента

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

	ALTER FRAGMENT ON TABLE имя_таблицы INIT IN имя_пространства

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

Если наша таблица занимает 10 экстентов по 100 килобайт, можно изменить размер следующего экстента со 100 до 500 килобайт. Тогда новый экземпляр будет занимать один экстент 100 килобайт и два экстента по 500 килобайт. Размер выделенного места несколько увеличится, но зато число экстентов сократится. К тому же мы таким образом зарезервируем некоторое количество памяти на перспективу.

Метод, к сожалению, не позволяет изменить размер начального экстента.

При выполнении ALTER FRAGMENT дополнительно (как побочный эффект):

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

2. Изменение размера начального экстента

Этот метод появился только в версии 11. Аналог предыдущего, но позволяет изменить размер только начального экстента

	ALTER TABLE имя_таблицы MODIFY EXTENT SIZE новый_размер_экстента

Теперь имеется возможность изменить оба размера.

Особенности ALTER FRAGMENT

При выполнении ALTER FRAGMENT необходимо иметь в виду, что эта операция выполняется в рамках транзакции. Следовательно, все страницы данных нового экземпляра должны поместиться в журнал транзакций СУБД - иначе возникнет ошибка Long transaction

Все ограничения целостности, определенные для таблицы, сохраняются. Все ссылки на таблицу сохраняются. Все представления, основанные на таблице, также сохраняются.

В том пространстве, куда перемещается таблица, должно быть достаточно свободного места.

Пересоздание таблицы

Если операция ALTER FRAGMENT по каким-либо причинам не может быть выполнена, остается только создать таблицу заново. Для этого нужно: