Borland Interbase / Firebird FAQ
Borland Interbase / Firebird Q&A, версия 2.02 от 31 мая 1999
последняя редакция от 17 ноября 1999 года.
Часто задаваемые вопросы и ответы по Borland Interbase / Firebird
Материал подготовлен в Демо-центре клиент-серверных технологий. (Epsylon Technologies)
Материал не является официальной информацией компании Borland.

E-mail mailto:delphi@demo.ru
www: http://www.ibase.ru/

Телефоны: 953-13-34

источники: Borland International, Борланд АО, релиз Interbase 4.0, 4.1, 4.2, 5.0, 5.1, 5.5, 5.6, различные источники на WWW-серверах, текущая переписка, московский семинар по Delphi и конференции, листсервер ESUNIX1, листсервер mers.com.

Cоставитель: Дмитрий Кузьменко

Этот документ содержит наиболее часто предоставляемую информацию по Borland IB Database.

Вопросы и ответы специфические для Interbase 6 / Firebird находятся в http://www.ibase.ru/v6/ib6faq.htm

 
1.1 Работает-ли IB с русскими буквами ?
1.2 Я установил Delphi 2.0 C/S, но почему-то использование русских кодировок приводит к ошибке ?
1.3 Почему таблица или select показываетс в Grid быстро, а перемещение в конец таблицы происходит долго ?
1.4 Как я могу работать с IB с клиентского компьютера ?
1.5 В каких версиях IB поддерживаются уведомлени о событиях (Event Alerts) ?
1.6 Сколько памяти использует IB на сервере ?
1.7 У меня возникают проблемы с открытием базы данных. USERNAME и PASSWORD вроде-бы правильные.
1.8 Можно-ли создать пользователя БД при помощи SQL-команды ?
1.9 Что такое ISC4.GDB ? Для чего нужна эта БД ?
1.10 Я создал БД с правами пользователей в Local IB. После переноса этой БД на IB for NT все пользователи куда-то "пропали". В чем дело ?
1.11 Как переносить базы данных между разными IB ? Например между Local IB и IB for NetWare ?
1.12 Почему IB работает неустойчиво на NetWare 4.x ?
1.13 Включение WAL на NetWare ухудшает производительность на 80% при вставках записей, и только на 15% - при обновлениях. Почему ?
1.14 Есть-ли способ восстановить несохраненные (uncommitted) изменения БД, например в случае отключения питания ?
1.15 Можно-ли в запросах делать поиск по BLOB ?
1.16 Почему я при вополнении запросов получаю "ошибку при создании файла 'SAA.AAA' ?
1.17 Можно-ли создать индекс по полю view ?
1.18 Почему в операторе SELECT для VIEW нельзя использовать ORDER BY ?
1.19 Как оптимизировать запросы ?
1.20 Какой максимальный размер базы данных IB ?
1.21 Сколько БД я могу открыть в одной транзакции ?
1.22 Сколько таблиц может быть в одной БД ?
1.23 Сколько строк и столбцов может быть у одной таблицы ?
1.24 Сколько индексов может быть в БД или у таблицы ?
1.25 Сколько событий можно вызвать из одной процедуры или триггера ?
1.26 Сколько таблиц можно объединять в join?
1.27 Сколько уровней вложенности может иметь запрос ?
1.28 Сколько полей можно использовать в одном составном (композитном) индексе ?
1.29 Какая глубина рекурсии допустима для триггеров или хранимых процедур ?
1.30 Какой максимальный размер текста хранимой процедуры или триггера?
1.31 Сколько UDF и фильтров blob можно определить для одной БД ?
1.32 Сколько клиентов могут зарегистрироваться на одно событие ?
1.33 Как освободить ресурсы, занятые IB ?
1.34 Как осуществить referental integrity между разными БД ? Или как связать таблицы из разных БД ?
1.35 Как скопировать данные между БД ?
1.36 Как конвертировать разные наборы символов ?
1.37 Поддерживает-ли IB репликацию и интерфейс X/Open XA ?
1.38 Можно-ли использовать имя таблицы как параметр хранимой процедуры ?
1.39 Может-ли IB шифровать данные в БД или при передаче по сети ?
1.40 Как определить дисковое пространство, необходимое для хранения БД ?
1.41 Как выбрать размер страницы БД ?
1.42 "Архитектура множественных поколений записи" приводит иногда к странным результатам - большое количество update без commit "тормозит" некоторые другие запросы, пока для БД не сделать "sweep". В чем причина ?
1.43 Поддерживает-ли IB поля типа autoincrement ?
1.44 На каком языке пишутся хранимые процедуры ?
1.45 Как сделать модуль UDF видимым IB ?
1.46 Есть-ли в IB функция SUBSTR и вообще, почему в IB так мало встроенных функций ?
1.47 Как можно создавать UDF ? (определяемые пользователем функции)
1.48 Что такое SHADOW в IB ?
1.49 Как посмотреть содержимое или обновить теневую (shadow) БД ?
1.50 Почему после выдачи Commit или RollBack при открытых таблицах или запросах они перечитываются полностью ?
1.51 Как избавиться от ошибки "multiple rows in singleton select" ?
1.52 Что такое "метаданные" ?
1.53 Где IB хранит информацию о метаданных ?
1.54 Почему невозможно использовать домены (DOMAIN) в хранимых процедурах ?
1.55 После удаления хранимой процедуры невозможно удалить таблицы, использовавшиеся в этой процедуре.
1.56 Как получить QUERY PLAN при работе с IB API ?
1.57 Мне нужны временные таблицы, но их нет в IB. Что делать ?
1.58 Что такое "глубина индекса" (index depth), показываемая в Database Analysis?
1.59 Как часто нужно делать Database Sweep ?
1.60 Запрос с вычислением максимального (MAX) значения работает медленно.
1.61 Можно-ли использовать stored procedures во view
1.62 Ошибка "low volume of metadata change" или Как часто можно менять метаданные (например изменять структуру таблиц)?
1.63 Многоверсионность метаданных, или как поддерживается модификация структур таблиц.
1.64 Почему рекомендуется периодически делать backup/restore ?
1.65 Как работает многоверсионность записей ?
1.66 Какие блокировки обеспечивает IB ?
1.67 Существуют-ли в IB блокировки по чтению ?
1.68 Какие уровни изоляции поддерживает IB ?
1.69 С какими транзакциями работают BDE, WISQL ... ?
1.70 Можно-ли управлять параметрами транзакций IB из BDE ?
1.71 Что такое BLR ?
1.72 Что такое UDF ?
1.73 Не восстанавливаются (restore) процедуры (sp) с QUERY PLAN. Что делать ?
1.74 Почему Server Manager не показывает Database Connections на WinNT ?
1.75 Что такое архитектура SuperServer ?
1.76 Соответствует-ли реализация SQL в IB стандарту ANSI и какому ?
1.77 Можно-ли поместить GDB-файл на другой компьютер ?
1.78 В чем разница между OAT и OIT ? (старейшей активной и старейшей заинтересованной транзакцией).
1.79 В чем разница между CHAR и VARCHAR ? Что лучше использовать ?
1.80 Можно-ли поместить БД IB на CD-ROM ?
1.81 Как выполнить create procedure/trigger при помощи TQuery?
1.82 Как установить генератор в нужное значение в процедуре или триггере?
1.83 Не могу подсоединиться к IB из под web-сервера IIS, Netscape, Baikonur и др
1.84 Ошибка при установке "Internal error near IBcheck"
 
1.1 Работает-ли IB с русскими буквами ?

Да, как с кодировкой 866 так и с 1251, как из Delphi 1.0, так и из Delphi 2.0. Для того, чтобы в БД в строковых полях использовались русские буквы, необходимо при создании БД указать в качестве дополнительного параметра фразу:

DEFAULT CHARACTER SET WIN1251

А в BDECFG32 в драйвере IB и его псевдонимах (aliases) указать языковый драйвер Pdox ANSI Cyrillic. Такие установки обеспечат нормальную работу с кодировкой 1251. (в TDatabase.Params этот языковый драйвер указывается как LANGDRIVER=ancyrr).

Для компонент прямого доступа необходимо в параметрах соответствующего компонента XXDatabase дописать
lc_ctype=WIN1251

Дополнительно, если вы предполагаете использовать выражение UPPER в SQL запросах, то Вам потребуется при создании таблиц IB использовать уточнение COLLATION SEQUENCE для строковых полей.

К сожалению, установить COLLATE PXW_CYRL по умолчанию для базы данных невозможно (такой параметр отсутствует). Поэтому таблицы IB, созданные при помощи Database Desktop или компонента TTable не будут иметь правильного COLLATE для работы с функцией SQL UPPER. (Эта проблема, необходимо отметить, не относится к работе с регистром русских букв внутри Delphi, поскольку для этого внутри VCL используются функции Windows, правильно переводящие буквы кодировки 1251 в верхний регистр и обратно.)

Например, если возможен запрос такого типа:

SELECT * FROM CUSTOMERS
WHERE UPPER(NAME) = 'ИВАНОВ'

То таблицу CUSTOMERS придется создавать при помощи текста

CREATE TABLE CUSTOMERS (
ID INTEGER NOT NULL,
NAME CHAR(30) COLLATE PXW_CYRL,
PRIMARY KEY (ID))

фраза COLLATE PXW_CYRL заставляет IB использовать таблицу трансляции символов PXW_CYRL вместо WIN1251 (которая устанавливается по умолчанию для DEFAULT CHARACTER SET WIN1251).

Однако можно использовать указание порядка сортировки "на ходу", без указания порядка сортировки в описании таблицы - при помощи того-же спецификатора COLLATE. В этом случае запросы могут выглядеть следующим образом:

SELECT * FROM CUSTOMERS
WHERE UPPER(NAME COLLATE PXW_CYRL) = 'ИВАНОВ'

Учтите, что если Вы используете ORDER BY NAME, то порядок записей у полей с COLLATE PXW_CYRL будет отличаться от имеющих только CHARACTER SET WIN1251. В этом случае необходимо и в ORDER BY указывать COLLATE. Например

SELECT * FROM CUSTOMERS
WHERE UPPER(NAME COLLATE PXW_CYRL) = 'ИВАНОВ'
ORDER BY NAME COLLATE PXW_CYRL

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

INSERT INTO MYTABLE VALUES (1, _win1251 'Привет!').

К сожалению, такой способ приводит к тому, что со стандартными компонентами TTable и TQuery можно работать только используя дополнительный компонент UpdateSQL (который позволяет переопределить запросы выдаваемые этими компонентами при INSERT, UPDATE, DELETE, и кроме этого присутствует только в Delphi 2.0).

ODBC-драйвер, поставляемый в дистрибутиве IB 5.5 позволяет указать кодировку win1251 для алиаса, при этом никаких манипуляций с _win1251 не требуется.

Подробнее см. документ http:/www.ibase.ru/devinfo/0109.htm

1.2 Я установил Delphi X.0 C/S, но почему-то использование русских кодировок приводит к ошибке ?

Проблемы с созданием БД с кодировкой WIN1251 могут быть если у Вас бета-версия Delphi. Еще одна причина - пробелы в имени каталога, в котором
находятся файлы локального IB (Program Files\Borland\Delphi X.0\IntrBase), или база данных (gdb-файл).

Если вам не хочется переустанавливать IB, то вы можете сделать следующее:

  • предварительно выгрузив локальный IB (если он виден на TaskBar), переместите каталог IntrBase в каталог, не содержащий в имени пробелов или на диск C: (C:\IntrBase).
  • Запустите RegEdit. В Registry реально к IB имеют отношение две строки: автоматический запуск Local IB и путь к файлу лицензии IB.
  • Найдите эти две строки и измените путь в этих строках на новый.

    1.3 Почему таблица или select показывается в Grid быстро, а перемещение в конец таблицы происходит долго ?

    Когда вы открываете набор данных при помощи TTable, BDE производит fetch только такого количества записей, которые помещаются в Grid. Если вы захотели переместиться в конец таблицы, то для большинства SQL-серверов возможно перемещение по записям только вперед - т.е. если вам потребуется поместить указатель в середину таблицы то BDE пришлось-бы заново перечитывать записи с ее начала. Причина такой работы в том, что SQL-серверы в большинстве возвращают результаты запросов в виде последовательных наборов записей. В навигационных БД напротив, возможно физическое позиционирование на любую запись таблицы.

    Если SQL-сервер поддерживает двунаправленные скроллируемые курсоры, то полное кэширование записей запроса выполняться BDE не будет.

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

    TTable-же напротив, показывает всю таблицу, и запросы для получения содержимого таблицы формируются автоматически. Поэтому TTable при нажатии вами в TDBGrid клавиш Ctrl-End сформирует запрос типа

    SELECT * FROM TABLE ORDER BY INDEXFIELD DESC

    и покажет только видимые в DBGrid записи "с конца". При этом, если нет DESC индекса по полю INDEXFIELD, сортировка данных (ORDER BY) будет производиться на диске. И чем больше записей в таблице, тем дольше. Для того, чтобы перемещение по Ctrl-End для TTable происходило быстро, нужно создать DESC индекс по полю сортировки. В этом случае операцию перехода в конец таблицы TTable выполнит практически мгновенно. Пользователи Delphi C/S могут посмотреть операторы, выдаваемые TTable SQL-серверу при помощи SQL Monitor.

    Более подробно на эту тему см. документ http://www.ibase.ru/devinfo/bde.htm

    1.4 Как я могу работать с IB с клиентского компьютера ?

    Можно использовать IB API (либо наборы компонт FreeIBComponents, IBObjects, IBX или FIBPlus, работающие напрямую с IB API), BDE+SQL Links, либо ODBC-драйвер.
    Схема обмена данными между этими компонентами следующая

    GDS32.DLL->IB прямое обращение к IB API
    ODBC->GDS32.DLL-> IB работа через ODBC
    BDE->SQL Link->GDS32.DLL->IB работа через BDE
    BDE->ODBC->GDS32.DLL->IB работа через BDE, ODBC вместо SQL Link.

    Практически во всех случаях вам не требуется производить какие-либо специфические настройки.

    1.5 В каких версиях IB поддерживаются уведомления о событиях (Event Alerts) ?

    Во всех, кроме локального IB для Windows 3.1. Это очень сильно урезанная версия IB, даже по сравнению с Local IB 4.1 for Windows95.
    Устойчивая работа Event Alerters зависит как от используемого компонента (см. www.ibase.ru/download.htm), так и от сервиспаков, установленных на NT Server или Workstation. Например, уведомление о событиях работает, если
    установлен SP3 и postsp3 hotfix TEARDROP2,
    либо установлен SP4 (большой, т.е. ~70мегабайт).

    EventAlerters могут работать неустойчиво, если для SP3 не установлен teardrop2, либо вообще не работать, если установлен SP4 размером ~32 мегабайта (из-за версии tcpip.sys). Если клиентами являются NT Workstation, настоятельно рекомендуется на клиентах установить тот же SP и постфиксы, что и для сервера.

    1.6 Сколько памяти использует IB на сервере ?

    Минимально при запуске - от 1.2 до 3 Мб, далее память распределяется динамически в зависимости от запросов, выполняемых клиентами (от 40 до 400К на клиента). Кроме этого, объем используемой памяти сильно зависит от архитектуры сервера (Classic или SuperServer), операционной системы, размера кэша страниц БД и других настроек. Подробнее по настройкам см. PERFORM.HLP в каталоге INTRBASE\BIN

    1.7 У меня возникают проблемы с открытием базы данных. USERNAME и PASSWORD вроде-бы правильные.

    Может быть, что в момент ввода пароля клавиатура переключена в режим Caps Lock - а пароль является регистрочувствительным. После установки действуют имя пользователя SYSDBA и пароль masterkey. Они предназначены для работы Администратора БД. Из соображений security вы должны изменить пароль masterkey, чтобы никто кроме вас не смог подсоединиться к IB. Также, ни в коем случае нельзя удалять пользователя SYSDBA, иначе вы потеряете доступ к системным таблицам.

    1.8 Можно-ли создать пользователя БД при помощи SQL-команды ?

    Нет. Единственно правильный способ - использовать Server Manager. (Tasks | User Security), либо утилиту командной строки GSEC, либо IB user API (для IB 5.x). См. www.ibase.ru/download.htm, www.borland.com/devsupport/bde/.

    1.9 Что такое ISC4.GDB ? Для чего нужна эта БД ?

    БД ISC4.GDB используется IB для хранения информации о пользователях (имена, пароли и т.п.). Удалять этот файл нельзя. Вы можете создать alias на эту БД и посмотреть ее содержимое, и даже программно добавлять пользователей или изменять их пароли, воспользовавшись соответствующими UDF в разделе www.ibase.ru/d_udf.htm.

    1.10 Я создал БД с правами пользователей в Local IB. После переноса этой БД на IB for NT все пользователи куда-то "пропали". В чем дело ?

    Причина в том, что информация о пользователях IB хранится в специальном файле ISC4.GDB, и является общей для всех БД на конкретном компьютере. Очевидно что в вашем случае на сервере IB for NT отсутствовали пользователи, заведенные вами для Local IB. Вам придется создать всех ваших пользователей и для IB for NT (при помощи Server Manager). То же самое будет и при переносе базы данных между серверами IB. Т.е. на обоих серверах пользователи должны быть созданы отдельно.

    Если версии IB и платформы на обоих серверах совпадают, то ISC4.GDB можно просто скопировать. Разумеется, при отсутствии подсоединений. (еще лучше вообще выключить сервер БД на время копирования isc4.gdb).

    Перенести isc4.gdb между платформами можно следующим способом: сделать backup isc4.gdb, восстановить на нужном сервере эту БД в другое имя (например isc4_new.gdb), остановить сервер IB, удалить старую isc4.gdb и переименовать isc4_new.gdb.

    1.11 Как переносить базы данных между разными IB ? Например между Local IB и IB for Linux?

    Для переноса нужно использовать операцию backup/restore, т.к. формат хранения данных для разных платформ разный. Переносить БД без backup/restore можно только в том случае, если у IB-источника и IB-приемника совпадает версия ODS - OnDisk Structure. Версию ODS можно увидеть в Server Manager после подсоединения к БД и вызову пункта меню Tasks/Database Statistics (Database Header, ODS version). Как правило, даже у одной и той же версии IB, но для разных операционных систем версия ODS разная.

    1.12 Почему IB работает неустойчиво на NetWare 4.x ?

    Для NetWare 4.x нужно использовать IB 4.2.2. Предыдущие версии IB for Netware сертифицированы только для NetWare 3.11/3.12.
    Для NetWare 5.x выпущена версия IB 5.6.

    Также нужно знать, что ни Interbase 6 ни Firebird не выпускаются и не будут выпускаться для Netware.

    1.13 Включение WAL на NetWare ухудшает производительность на 80% при вставках записей, и только на 15% - при обновлениях. Почему ?

    Файл WAL должен быть расположен на другом винчестере чем основная БД. (это-же относится и к теневой БД). В этом случае ухудшения производительности не будет. Кроме этого вы должны учитывать, что запись в WAL происходит синхронно с БД, поэтому сравнивать "производительность" WAL с асинхронными изменениями в БД некорректно.

    1.14 Есть-ли способ восстановить несохраненные (uncommitted) изменения БД, например в случае отключения питания ?

    Нет. Такие изменения будут потеряны. Т.е. БД останется в состоянии, соответствующем последней подтвержденной (committed) транзакции. Несохраненные данные в БД останутся в виде "осиротевших" страниц, которые можно очистить при помощи Server Manager, пункт меню Validate Database, или в виде "мусорных" версий записей, которые будут автоматически очищены при очередном чтении данных.

    Вполне возможно, что этот вопрос вызван тем, что восстановление состояния БД в IB отличается от других широкораспространенных SQL-серверов (Oracle, Informix, ...). IB не требует выполнения каких-то специфических действий для продолжения работы с БД в случае сбоя питания - изменения, происходящие до COMMIT, не записываются на место актуальных данных (т.е. отсутствует режим Dirty Read), поэтому они будут просто потеряны, а БД останется в рабочем состоянии.

    примечание: считается что транзакция - логический блок действий, переводящий БД из одного целостного состояния в другое. Поэтому даже если сервер позволяет восстановить uncommitted изменения, то делать этого не стоит.

    1.15 Можно-ли в запросах делать поиск по BLOB ?

    Да. Поиск по строковым (CHAR, VARCHAR) полям или по BLOB можно производить при помощи операторов CONTAINING, STARTING WITH и LIKE. Например

    SELECT * FROM MYTABLE
    WHERE BLOBFIELD CONTAINING 'sometext';

    Поиск по умолчанию считается case-insensitive (регистро-нечувствительный), поэтому для латинских букв строку поиска можно задавать строчными буквами (в нижнем регистре). В этом случае при поиске 'sometext' в ответ войдут записи с 'sometext', 'SOMETEXT' и 'SomeText'. К сожалению, для BLOB невозможно указать COLLATE для правильного перевода русских букв в верхний регистр, поэтому поиск слов, содержащих русские буквы, будет производиться только по точному совпадению.

    При поиске подтип BLOB (SUB_TYPE 0 или 1 - текст или binary) не имеет значения, т.к. подтип имеет значение только для приложения, или для фильтров BLOB. BLOB-ы разных подтипов хранятся абсолютно одинаковым способом.

    1.16 Почему я при вополнении запросов получаю "ошибку при создании файла 'SAA.AAA' ?

    Этот файл является временным, и создается когда при выполнении запроса возникает необходимость в сортировке результата. Ошибка может возникать при нехватке дискового пространства на томе, куда указывает TEMP. Для NT при работе IB в режиме сервиса необходимо изменить переменную TEMP для System (см. My Computer/Properties/Environment). Для IB 5.x и 6.0 (в архитектуре SuperServer только!) можно указать диски и размер временных файлов в файле конфигурации IBCONFIG, или в переменной окружения INTERBASE_TMP (см. Operations Guide, стр92). Например:

    TMP_DIRECTORY "c:\" 10000000
    TMP_DIRECTORY "e:\temp\" 100000000
    Может быть указано несколько дисков или каталогов, которые будут использоваться последовательно. Размер должен быть указан в байтах. Кавычки для имени диска и каталога обязательны.

    1.17 Можно-ли создать индекс по полю view ?

    Нет. Индекс должен быть у поля таблицы, входящей во view. View является видом на таблицу или несколько таблиц, поэтому не может иметь физического индекса как у обычной таблицы.

    Если вы хотите использовать индексы в компоненте TTable, то делать это нужно не через свойство IndexName, а указывая поле, по которому вы хотите отсортировать таблицу или view, непосредственно в свойстве IndexFieldNames. SQL-сервер сам разберется, есть-ли по такому полю индекс, и сможет-ли он использовать его для ускорения обработки запросов.

    1.18 Почему в операторе SELECT для VIEW нельзя использовать ORDER BY ?

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

    1.19 Как оптимизировать запросы ?

    Вот несколько простых рекомендаций для оптимизации запросов:

  • пробуйте разный порядок таблицы в запросе для получения оптимальной производительности (также и для явного join)
  • пробуйте ваши запросы в WISQL, включив Show Query Plan и Show Statistics.
  • используйте индексы по полям, участвующим в условиях where.
  • не забывайте делать SET STATISTICS по индексам, созданным вами специально для ускорения запросов.

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

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

    1.20 Каков максимальный размер базы данных IB ?

    Borland и независимые эксперты сообщают, что IB может обслуживать файлы БД размером в ~10-20 гигабайт. Существует также ограничение на размер одного файла БД, которое в основном зависит от ограничений используемой файловой системы, и составляет от 2 до 4 гигабайт. В любом случае не стоит допускать увеличения размера одного файла БД выше 2 гигабайт (создайте один или более вторичных файлов заранее). Многофайловая БД может состоять из 65535 файлов, таким образом теоретический предел для одной базы данных IB - 132 терабайта.

    Известны реально используемые базы данных в 180 гигабайт и 980 гигабайт, не содержащие blob-полей.

    1.21 Сколько БД я могу открыть в одной транзакции ?

    Не существует архитектурнго ограничения на такую возможность. Пользователи сообщают о работе одновременно с 17-ю БД.

    1.22 Сколько таблиц может быть в одной БД ?

    65,536. Количество определяется переменной типа SHORT.

    1.23 Сколько строк и столбцов может быть у одной таблицы ?

    Максимальное количество записей в таблице зависит от среднего размера записи, и не может превышать 2 миллиардов. Размер записи может быть не более 64К, и максимальное количество столбцов зависит от используемых типов полей. Длина BLOB в это число не входит, т.к данные blob хранятся отдельно от основной записи.
    Чем больше количество столбцов в таблице и чем больше записей в таблице, тем больший размер страницы базы данных имеет смысл выбирать. В настоящее время рекомендуется выбирать page_size не менее 4K.

    1.24 Сколько индексов может быть в БД или у таблицы ?

    Можно создать в одной БД до 65535 индексов. Все они могут быть и для одной таблицы.

    1.25 Сколько событий можно вызвать из одной процедуры или триггера ?

    Ограничений на количество POST_EVENT нет, однако на клиентском месте ограничения могут быть. Например компонент IBEventAlerter в поставке Delphi может зарегистрировать максимально 15 событий.

    1.26 Сколько таблиц можно объединять в join?

    Нет архитектурных ограничений. Пользователи сообщают о запросах одновременно по 15-и таблицам.

    примечание: оптимизатор 5.x для более чем 7-и объединяемых явным или неявным join производит операцию SORT MERGE (сортировка слиянием). Это приводит к сильному замедлению запроса как минимум прямо пропорционально количеству записей в таблицах. Эту проблему можно исправить только если запрос вроде

    select H.id, ...
    from H, A, B, C, D, E, F, G
    where a.id = h.aid and b.id = h.bid and c.id = h.cid and d.id = h.did and e.id = h.eid and f.id = h.fid and g.id = h.gid

    привести к виду

    select H.ID,
    H.AID, (select A.NAME from A where A.ID = H.AID),
    H.BID, (select B.NAME from B where B.ID = H.BID),
    H.CID, (select C.NAME from C where C.ID = H.CID),
    H.DID, (select D.NAME from D where D.ID = H.DID),
    H.EID, (select E.NAME from E where E.ID = H.EID),
    H.FID, (select F.NAME from F where F.ID = H.FID),
    H.GID, (select G.NAME from G where G.ID = H.GID)
    from H

    1.27 Сколько уровней вложенности может иметь запрос ?

    Нет архитектурных ограничений. Пользователи сообщают о запросах вложенностью около 16-и.

    1.28 Сколько полей можно использовать в одном составном (композитном) индексе ?

    До 16 полей. Обратите внимание, что в документации по IB сказано: "создание foreing key по двум и более полям ошибки не вызывает, но не обрабатывается". Кроме этого, оптимизатор будет использовать такой индекс только в том случае, если в предложении WHERE или ORDER BY указаны все поля, составляющие этот индекс, и для ORDER BY именно в том порядке, в котором они упоминаются в индексе.

    1.29 Какая глубина рекурсии допустима для триггеров или хранимых процедур ?

    Триггеры и хранимые процедуры имеют ограничение в 1000 рекурсивных вызовов под UNIX, и около 700 - под NT. Реально это число меньше, и зависит от количества локальных переменных в хранимой процедуре, а также от объема текста самой процедуры.

    Рекурсия для триггеров означает например выполнение UPDATE в триггере BEFORE UPDATE для одной и той же таблицы, или выполнение в триггере процедуры, которая модифицирует эту же таблицу, что вызывает срабатывание того же триггера.

    1.30 Какой максимальный размер текста хранимой процедуры или триггера?

    Размер скомпилированной процедуры или триггера может быть до 48K. (скомпилированные процедуры и триггеры находятся в blob-полях RDB$TRIGGER_BLR и RDB$PROCEDURE_BLR соответствующих системных таблиц RDB$TRIGGERS и RDB$PROCEDURES).

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

    1.31 Сколько UDF и фильтров blob можно определить для одной БД ?

    Имя UDF ограничено длиной в 31 символ. Количество параметров UDF - не более 10. Других ограничений нет.

    1.32 Сколько клиентов могут зарегистрироваться на одно событие ?

    Такого ограничения не существует.

    1.33 Как освободить ресурсы, занятые IB ?

    Сделайте серверу IB ShutDown при помощи Server Manager.

    1.34 Как осуществить referental integrity между разными БД ? Или как связать таблицы из разных БД ?

    Автоматической поддержки RI между разными БД нет (т.к. таблицы одной БД не видны из другой). Вам придется это делать программно, используя two-phase commit при прямом доступе к IB либо из разных TDatabase через BDE. В последнем случае при объединении таблиц записи будут извлечены из БД на клиентскую часть и только затем объединены.

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

    1.35 Как скопировать данные между БД ?

    Такая возможность присуствует в IB 4.0, 4.1 (утилита QLI) однако отсутствует в IB 4.2 и 5.x. В простейшем случае можно обойтись утилитами DATAPUMP или Database Desktop. Также большое количество подобных утилит есть на www.ibase.ru/download.htm. В конце-концов, можно достаточно быстро самому написать программу копирования данных.

    1.36 Как конвертировать разные наборы символов ?

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

    1.37 Поддерживает-ли IB репликацию и интерфейс X/Open XA ?

    IB не поддерживает репликацию или X/Open в версиях 4.0, 4.1, 4.2, 5.0, 5.1, 5.5, 5.6.
    Вместе с IB 6.0 поаставляется репликатор, который все равно представляет собой отдельный инструмент, который работает с Interbase как любая другая программа. В настоящее время существует несколько сторонних реализаций репликаторов, найти которые можно на www.ibase.ru.

    1.38 Можно-ли использовать имя таблицы как параметр хранимой процедуры ?

    Нет, но можно передать какое-либо строковое или числовое значение, чтобы обработать его по IF .. ELSE.

    Хранимая процедура не может динамически получать имя таблицы. (похоже что причиной является то, что текст хранимой процедуры "скомпилирован" в BLR, который невозможно интерпретировать).

    1.39 Может-ли IB шифровать данные в БД или при передаче по сети ?

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

    1.40 Как определить дисковое пространство, необходимое для хранения БД ?

    Точно посчитать размер БД не представляется возможным. Однако для предсказания размера БД можно использовать определенные утверждения:

  • При импорте данных страницы (data pages) заполняются наполовину, если для базы данных не указан флаг no_reserve (по умолчанию). Таким образом объем импортированных данных будет в два раза больше (если данные импортируются из текстовых файлов). Размер ключа индекса в байтах равен 5 + длина поля, однако индексы упаковываются даже при вставке записей.
  • При импорте данных страницы индексов заполняются тем плотнее, чем меньше разница между индексируемыми значениями. Если IB не может произвести сжатие ключей, то индексные страницы будут заполнены наполовину.
  • При восстановлении (restore) данных страницы данных будут заполнены опять-же наполовину, а страницы индексов - полностью.
  • Поля типов CHAR и VARCHAR хранят столько символов, сколько занимает значение поля.
  • Строковые поля и также числовые последовательности при обновлении могут быть упакованы по алгоритму RLE.
  • BLOB-поля хранятся сегментами (по умолчанию 80 байт), т.е. на одной странице данных может помещаться несколько значений BLOB если их суммарный размер меньше размера страницы.

    Необходимо также учитывать, что при работе с БД возможно динамическое создание страниц, необходимых для хранения версий записей. Эти страницы не возвращаются файловой системе даже при операции database sweep. Как следствие этого, БД занимает минимально возможный объем только после операций backup/restore, и максимальный - при частом обновлении данных большим количеством пользователей.

    1.41 Как выбрать размер страницы БД ?

    Этому посвящен один из разделов IB Data Definition Guide. Имеет смысл увеличить до 4К (с умолчательного 1К) размер страницы, если большее кол-во таблиц в вашей БД имеет количество записей > 200000 (200 тыс.). Не забудьте, что увеличение размера страницы повлечет увеличение размера памяти, отводимого под кэш-буфер IB, т.к. его размер измеряется в страницах.

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

    1.42 "Архитектура множественных поколений записи" приводит иногда к странным результатам - большое количество update без commit "тормозит" некоторые другие запросы, пока для БД не сделать "sweep". В чем причина ?

    Архитектура множественных поколений записи требует "кооперативной сборки мусора". Это означает что каждый запрос который обнаружит неактуальные записи должен удалить их. Это позволяет значительно уменьшить блокировки и ускорить операции commit/rollback. Однако, как вы заметили, иногда выполнение запросов может сильно замедляться если на диске много "устаревших" версий записей. Это происходит из-за того, что изменения записываются как delta между старой и новой записью, и при чтении новой записи IB должен ее "собрать" из старых данных и delta. Для оптимизации "сборки" записей IB заполняет страницы данных только наполовину, оставляя вторую половину для хранения возможных delta. Если это пространство исчерпывается, то IB распределяет новые страницы для хранения delta. В этом случае ухудшается IO (для сборки записей приходится читать дополнительные страницы), которое и приводит к ухудшению общей производительности.

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

    Другие SQL-серверы, которые не поддерживают множественные поколения записей, могут порождать проблемы другого рода - для запросов с изоляцией Repeatable Read либо полное дублирование данных, либо полное блокирование обновляемых таблиц.

    Кроме того, logging в других SQL-серверах может вызывать похожее ухудшение производительности.

    1.43 Поддерживает-ли IB поля типа autoincrement ?

    В явном виде нет - вместо этого в IB существует другой механизм, называемый "генераторами". Генератор - некая переменная, значение которой может быть получено и увеличено на некоторое значение (дельту) при помощи встроенной функции GEN_ID. Создать генератор можно фразой

    CREATE GENERATOR MYGENERATOR;

    Обычно генераторы используют в триггерах, при этом текст триггера может быть следующим:

    CREATE TRIGGER TI_CLIENTS FOR CLIENTS
    ACTIVE BEFORE INSERT POSITION 0
    AS
        BEGIN
            IF (new.CLIENT_ID IS NULL) THEN
                CLIENT_ID = GEN_ID(MYGENERATOR, 1);
        END

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

    Также новое значение генератора можно получить запросом
    SELECT GEN_ID(mygen, 1) from RDB$DATABASE

    Механизм генераторов гарантирует что даже при конкурентном (параллельном) вызове функции GEN_ID каждому пользователю будет выдаваться уникальное значение. Последнее значение генератора всегда запоминается в БД, поэтому разработчику не нужно заботиться о "восстановлении" его максимального значения после подсоединения к БД.

    Генераторы являются переменными типа integer (longint), таким образом если предположить что новое значение возвращается в среднем с интервалом в 3 секунды, значений генератора хватит приблизительно на 270 лет.

    примечание: если вы воспользуетесь приведенным выше примером использования генератора в триггере, то у вас может возникнуть следующая проблема - при добавлении записей с клиентского места новые записи будут "пропадать", или будет появляться сообщение BDE "Record/Key deleted". Это связано с тем, что клиенту никаким образом не может быть передана информация об идентификаторе сформированом в триггере на сервере.

    Т.е. новую запись можно будет увидеть только либо перевыполнив запрос либо переместившись в конец таблицы (если еще не произошел fetch всех записей. Для исключения такой ситуации можно создать хранимую процедуру возвращающую значение генератора (так-же как и для триггера), и вызывать эту процедуру _перед_ созданием новой записи (для Delphi - TTable.BeforePost). Вместо процедуры можно использовать приведенный выше запрос select gen_id(mygen, 1) from rdb$database.

    Подробнее см. документ http://www.ibase.ru/devinfo/generator.htm

    1.44 На каком языке пишутся хранимые процедуры ?

    Хранимые процедуры пишутся на языке SQL включая некоторые расширения - begin, end, exception, exit, if then else и т.д. За более подробной информацией обращайтесь к IB Language Reference или IB Data Definition Guide (в печатном или электронном виде).

    1.45 Как сделать модуль UDF видимым IB ?

    DLL с UDF нужно поместить либо в тот каталог, где находится исполняемый файл IB, либо в WINDOWS\SYSTEM либо в WINNT\SYSTEM32.Не имеет смысла помещать UDF DLL в каталог, где находится БД.

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

    примечание: вы должны учитывать особенности работы с UDF в IB версий 4.2 и выше (с архитектурой SuperServer).

    В Interbase 6 или Firebird библиотеки пользовательских функций (UDF) должны быть помещены в каталог UDF, или в каталог, определенный в файле конфигурации IBCONFIG (или isc_config на Unix) параметром EXTERNAL_FUNCTION_DIRECTORY. Это сделано по соображениям безопасности.

    1.46 Есть-ли в IB функция SUBSTR и вообще, почему в IB так мало встроенных функций ?

    Функции SUBSTR в IB нет, но такую функцию можно достаточно просто написать и подключить к IB. Начиная с версии 5.0 SubStr и другие функции поставляются в комплекте с IB, однако для их использования их нужно объявить в базе данных явно.

    примечание: Firebird начиная с билда 0.9.5.200 поддерживает стандартную функцию SUBSTRING(field FROM n FOR m) без необходимости объявления ее DECLARE VARIABLE для любой базы данных).

    1.47 Как можно создавать UDF ? (определяемые пользователем функции)

    Определяемые пользователем функции (UDF) могут быть на любом языке программирования (компилятор которого поддерживает создание DLL), например на Delphi 2.0 для IB 4.0 for Windows NT или Windows 95, и на C для IB под Windows- и Unix-платформах.

    Для ознакомления с написанием UDF на Delphi можно обратиться к UDF Starter Kit, распространяемому ДемоЦентром. Это наиболее полное описание того, как можно писать UDF и какие параметры в них можно обрабатывать. Вместе с тем предлагаемые функции можно использовать как готовый набор.

    1.48 Что такое SHADOW в IB ?

    Shadow - это программное "зеркалирование" БД. Все операции записи, производимые над каким-либо файлом GDB параллельно производятся и над соответствующим файлом SHADOW. При сбое GDB вы можете остановить работу пользователей и просто скопировать Shadow на место оригинальной БД (GDB), после чего продолжить работу. Необходимо учитывать, что поддержка Shadow замедляет операции изменения БД. Желательно чтобы Shadow располагалась на другом винчестере, и еще лучше если винчестеры с GDB и Shadow будут иметь разные контроллеры - в этом случае запись будет распараллеливаться.

    В ДемоЦентре проводились элементарные тесты на Local IB 4.1 (из комплекта Delphi 2.0) под Windows95. Тест представлял собой добавление 10000 (десять тысяч) записей в пустую БД, каждые 1000 записей обрамлялись StartTransaction-Commit. Использовался HDD с контроллером IDE: (одна запись = ~100 байт + BLOB-поле 512К)

     усредненное время

     
    без Shadow   4мин 40сек 
    с Shadow на том-же винчестере   6мин 00сек 
    с Shadow на другом винчестере   4мин 50сек 

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

    без Shadow   1 
    с Shadow на том-же винчестере   1.6 (минимум в полтора раза) 
    с Shadow на разных винчестерах   ~1 (почти без замедления) 

     Кроме того, очень сильное влияние на быстродействие IB оказывает параметр Forced Writes - немедленное сохранение изменений страниц. Вы можете включить или выключить этот параметр для конкретной БД на ходу при помощи Server Manager. При выключенном Forced Writes работа с БД происходит в 5-6 раз быстрее, но есть опасность потерять БД при внезапном выключении питания сервера.
    примечание: при работе со SCSI-устройствами, или IDE с драйвером BusMaster, разница при работе с включенным или выключенным Forced Writes будет минимальна, если вообще заметна.

    1.49 Как посмотреть содержимое или обновить теневую (shadow) БД ?

    Такие операции нельзя проводить над теневой БД. IB автоматически обеспечивает полное соответствие оригинальной БД и ее тени. Изменения в теневой БД происходят не на уровне транзакций, а на уровне модификаций страниц основной БД.

    1.50 Почему после выдачи Commit или RollBack при открытых таблицах или запросах они перечитываются полностью ?

    Начнем с того, что такое перечитывание в BDE происходит не всегда. Если вы открываете TTable или TQuery в контексте какой-либо транзакции, то содержимое этих источников данных определяется уровнем изоляции транзакции (ReadCommitted или RepeatableRead). Если при открытых источниках данных завершить транзакцию подтверждением или откатом, то контекст транзакции сменится - вступит в действие т.н. неявная транзакция, которая стартует после соединения с БД. В результате данные, прочитанные до смены контекста могут стать неактуальными, поэтому BDE фактически переоткрывает курсоры. А поскольку неизвестно, на какой строке выборки завершилась явная транзакция, считываются все записи (fetch до конца таблицы).

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

    Более подробную информацию о поведении BDE при commit/rollback вы сможете получить в BDE32.HLP, пункт DbiEndTran.

    1.51 Как избавиться от ошибки "multiple rows in singleton select" ?

    Очевидно что данная ошибка происходит в вашем триггере или хранимой процедуре. Обычный SELECT внутри триггера или процедуры должен возвращать одну строку (row), т.к. при двух и более строках IB не знает куда поместить значения полей этих строк. Если ваш SELECT возвращает несколько записей, то нужно пользоваться конструкцией FOR SELECT ... INTO ... DO ... которая производит обработку возвращаемого набора записей в цикле.

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

  • существуют таблицы ORDERS (заказы) и CLIENTS (клиенты).
  • обе эти таблицы имеют поле связи CLIENT_ID INTEGER.
  • для того чтобы вытащить информацию о клиенте используется запрос:

    SELECT CLIENT_ID, CLIENT_NAME
    FROM CLIENTS
    WHERE CLIENT_ID = ?

    где ? - либо значение либо переменная.

    Теперь представим себе, что этот запрос должен выполняться в триггере при вставке записи в таблицу ORDERS

    CREATE TRIGGER TI_ORDERS FOR ORDERS
    ACTIVE AFTER INSERT POSITION 0
    AS
    DECLARE VARIABLE CID INTEGER;
    DECLARE VARIABLE CNAME CHAR(30);
    BEGIN
        SELECT C.CLIENT_ID, C.CLIENT_NAME
        FROM CLIENTS C
        WHERE C.CLIENT_ID = CLIENT_ID
        INTO :CID, :CNAME;
        ...

    Итак, поскольку в запросе использован псевдоним C (FROM CLIENTS C), то якобы существует гарантия что в предложении WHERE будут сравниваться поле C.CLIENT_ID из таблицы CLIENTS и поле CLIENT_ID из таблицы ORDERS (в триггере доступны имена полей собственной таблицы). На самом деле даже использование псевдонимов не дает гарантии что переменные будут разичаться, и получается что в предложении WHERE сравнивается само с собой поле таблицы CLIENTS.CLIENT_ID, и в запросе возвращается ВСЯ таблица CLIENTS.

    Вот почему возникает вышеупомянутое сообщение об ошибке.

    Избавиться от него можно несколькими путями:

  • Использовать разные имена полей для связи между CLIENTS и ORDERS. например OCLIENT_ID и CCLIENT_ID.
  • Использовать уточнитель new.CLIENT_ID, несмотря на то что в документации указано что для триггеров последействия (AFTER) он не имеет смысла.

    SELECT C.CLIENT_ID, C.CLIENT_NAME
    FROM CLIENTS C
    WHERE C.CLIENT_ID = new.CLIENT_ID
    ...

  • Перед запросом поместить CLIENT_ID в локальную переменную, и в запросе использовать сравнение не с полем, а с этой локальной  переменной.

    CID=CLIENT_ID;
    SELECT C.CLIENT_ID, C.CLIENT_NAME
    FROM CLIENTS C
    WHERE C.CLIENT_ID = :CID
    ...

    1.52 Что такое "метаданные" ?

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

    1.53 Где Borland IB хранит информацию о метаданных ?

    IB хранит информацию о метаданных в таблицах, имена которых начинаются с RDB$. Например, информация о связях между таблицами находится в таблице RDB$RELATIONS. Список системных таблиц и их назначение вы можете посмотреть в книге Borland IB Language Reference (или IBLR.PDF в электронном виде. находится в каталоге \MANUALS дистрибутива Delphi 1.0.x)

    ! Не рекомендуется вносить изменения напрямую в системные таблицы, этим можно испортить БД. При необходимости изменения системных таблиц вы должны руководствоваться IB Language Reference !

    1.54 Почему невозможно использовать домены (DOMAIN) в хранимых процедурах ?

    Отвечает один из разработчиков IB David Schnepper:

    "Потому что мы не стали реализовывать это. Попробую вспомнить причины, хотя это было около 4-х лет назад:

  • достаточно трудно осуществлять domain validation в параметрах ввода/вывода SP (например define domain age smallint check (age >=0 and age <=150);)
  • также трудно обеспечивать изменение доменов-параметров SP при модификации домена. (например, используем AGE в SP, затем даем команду alter domain age float. С другой стороны, в реализации SQL в IB нет возможности модифицировать домен, но это возможно операторами GDML). Такое изменение домена приведет к необходимости перекомпиляции SP, а в любой момент времени нельзя гарантировать что SP не используется в момент изменения домена.
  • в настоящее время стандарт "draft SQL PSM", на котором базируется наша реализация SP, не разрешает использовать домены в SP. Кроме того, я не в курсе текущей ситуации со этим стандартом.

    Безусловно, ни один из этих пунктов не облегчит вам жизнь, но хотя-бы даст понимание почему данная возможность не реализована в IB 4.0, 4.1, 4.2".

    1.55 После удаления хранимой процедуры невозможно удалить таблицы, использовавшиеся в этой процедуре.

    После drop procedure нужно выдать commit, и только после этого делать drop table. В крайнем случае нужно убедиться, что процедура действительно удалена, и удалить запись с ее именем в таблице RDB$DEPENDENCIES. Этот баг исправлен в IB 5.x.

    1.56 Как получить QUERY PLAN при работе с IB API ?

    Нужно использовать функцию isc_dsql_sql_info(), запросив данные isc_info_sql_getplan. К сожалению, пользоваться этой функцией для запросов, выдаваемых через BDE невозможно, т.к. она требует предварительного вызова isc_dsql_prepare, которая в свою очередь использует native transaction handle (недоступный в BDE).

    1.57 Мне нужны временные таблицы, но их нет в IB. Что делать ?

    IB не расчитан на частое изменение метаданных, поэтому создание таблицы на ходу и ее удаление не является хорошим приемом. Вместо временной таблицы вы можете создать постоянную таблицу для тех-же целей с первым полем равным USER (и входящим в первичный ключ). В этом случае даже если несколько пользователей начнут добавлять записи в такую таблицу, конфликта между ними не будет. Вместо USER можно использовать генератор (generator), который будет действовать на все время работы пользователя. Тогда если ваше приложение не использует регистрацию IB, и все USER одинаковы, генераторы предотвратят конфликт между пользователями.

    1.58 Что такое "глубина индекса" (index depth), показываемая в Database Analysis?

    "Глубина индекса" это то-же что и глубина B-дерева. Если вам незнаком этот термин, то следует обратиться к соответствующей литературе. Вообще глубиной индекса определяется скорость поиска в индексе - т.е. именно столько страниц придется прочитать IB чтобы найти указатель на нужную запись (при поисках типа WHERE, но не ORDER BY). Естественно, что чем меньше глубина индекса тем лучше. Оптимальной считается 3.

    Если при просмотре в Database Analysis (или GSTAT) глубина какого-то индекса окажется 4 или выше, то его необходимо деактивировать и активировать обратно (т.е. перестроить - set index inactive/active). Это не всегда может помочь, т.к. на глубину индекса в первую очередь влияет длина ключа и количество записей в таблице.

    Кроме того, при анализе заполнения индексных страниц (fill distribution) нужно учитывать, что IB заполняет страницы в среднем наполовину для оптимизации изменений индексов (или данных) транзакциями. Т.е. заполнение страниц 40-59% можно считать нормальным.

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

    1.59 Как часто нужно делать Database Sweep ?

    БД имеет по умолчанию параметр начала Sweep в 20000 транзакций. Это может показаться большим, но для реальных приложений 20000 транзакций - именно то число, когда в БД скапливается достаточно "мусора".

    Количество "мусора" в БД определяется тем, насколько часто происходят вставки, изменения и удаления. Версии записей удаляются только тогда, когда они не нужны старейшей активной транзакции (OAT).

    Вообще "sweep interval" - это разница между OAT и OIT (старейшей активной и старейшей заинтересованной транзакцией), при котором необходимо делать sweep. Если номер OIT увеличивается также, как и OAT, автоматический sweep может и не наступить. Одной из наиболее вероятных причин увеличения номера OIT является завершение транзакции операцией ROLLBACK.

    При выполнении Sweep освободившиеся страницы не возвращаются операционной системе - они будут использоваться IB как свободные страницы.

    Если вы хотите самостоятельно и регулярно делать Sweep, то можно это делать, например в ночное время когда нет работающих пользователей, при помощи утилиты GFIX. Кроме того, выключить автоматический sweep можно установив "sweep interval" = 0.

    примечание: следить за OAT-OIT можно периодически получая статистику по конкретной БД при помощи Server Manager (или утилиты GSTAT).

    примечание: например если вы вставите в таблицу 1000000 (миллион) записей, завершая вставку каждой операцией commit, то автоматический sweep запущен не будет (OAT-OIT постоянно и очень мало). Однако так поступать тоже не стоит, т.к. вставка будет происходить очень медленно - лучше всего в одной транзакции вставлять 100-200 записей.

    примечание: в IB 5.5 есть баг, который приводит к падению сервера при SWEEP, если в базе данных есть индексы по полям, возможно содержащим значения NULL. В этом случае необходимо отключить автоматический SWEEP (установить в 0), а операцию backup производить с установленным флагом Disable garbage collection. Данный баг исправлен в IB 5.6.

    1.60 Запрос с вычислением максимального (MAX) значения работает медленно.

    Да, к сожалению оптимизатор IB 4.x не использовал индексы при вычислении MAX, даже если специально указать условие выбора или сортировки по полю, имеющему индекс. А для IB 5.x индексы могут быть использованы как однонаправленные - ASC индекс для функции MIN, и DESC индекс для функции MAX.

    Тем не менее, проблему можно решить следующим способом:

    Создать индекс по убыванию (DESC) по полю, которое используется в MAX. Если вы работаете из Delphi, то нужно выполнить следующий запрос

    SELECT FIELD FROM TABLE ORDER BY FIELD DESC

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

    Однако если вы попытаетесь выдать такой запрос например в WISQL или ISQL, то кроме выполнения запроса будут получены еще и все записи, что может занять достаточно длительное время. В этом случае нужно создать хранимую процедуру, возвращающую результат такого запроса (FOR SELECT ...). WISQL не выбирает все записи из хранимой процедуры, а только первую. Ею и окажется нужная вам запись. Эту-же хранимую процедуру можно использовать и из Delphi (StoredProc1.ExecProc) не прибегая к помощи компонента TQuery.

    примечание: не стоит использовать MAX для генерации уникальных идентификаторов - вместо этого лучше использовать механизм генераторов.

    1.61 Можно-ли использовать stored procedures во view

    Вообще это нарушает правила построения VIEW, т.к. VIEW должны основываться на таблицах, и возвращать записи в их естественном порядке (так как они хранятся в таблицах). В хранимой процедуре возможна выдача как "псевдотаблиц" (вычисляемые значения) так и записей таблиц в отличном от естественного порядке (FOR SELECT с ORDER BY).

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

    К сожалению, в IB отсутствует контроль за использованием SP во view поэтому для версии 4.1 такое view создать просто невозможно (GPF), а в версии 4.2 такое view не работает (GPF при SELECT * FROM MYVIEW).

    1.62 Ошибка "low volume of metadata change" или Как часто можно менять метаданные (например изменять структуру таблиц)?

    существует ограничение на 255 модификаций метаданных между backup/restore. Например

    create table changes_a_lot (a smallint); /* версия 0 */
    alter table changes_a_lot add b smallint; /* версия 1 */
    ...
    alter table changes_a_lot drop b; /* версия 254 */
    alter table changes_a_lot add constraint ... /* ой! версия 255 ! */

    Кроме этого счетчик метаданных таблицы увеличивается при каждой модификации триггера этой таблицы, в том числе и при ACTIVE/INACTIVE триггера.

    Вообще метаданные изменяются нечасто если ваша БД находится в промышленном использовании, а не на стадии разработки.
    Так что если вы получили подобное сообщение об ошибке - сделайте backup и restore вашей БД, и продолжайте работать.

    примечание: см. материал по версиям метаданных.

    1.63 Многоверсионность метаданных, или как поддерживается модификация структур таблиц.

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

    1.64 Почему рекомендуется периодически делать backup/restore ?

    Вообще, backup предназначен для резервирования данных на случай сбоя системы. Кроме того, backup/restore делает несколько полезных вещей:

  • сбрасывает счетчики версий метаданных
  • перепаковывает записи на страницах данных
  • убирает старые версии записей (как sweep)
  • перестраивает индексы
  • возвращает дисковое пространство операционной системе
  • очищает "дыры" в многофайловой БД
  • при восстановлении можно изменить размер страницы.

    1.65 Как работает многоверсионность записей ?

    Многоверсионность записей работает следующим образом:

  • при удалении запись помечается на удаление, и может быть удалена только после commit этой транзакции. Если транзакции repeatableread, видящие такую запись, все еще активны, то запись остается "неудаленной" до тех пор пока они не завершатся. Когда в записи никто не заинтересован, она помечается на окончательное удаление и будет физически удалена либо при database sweep, либо при старте другой транзакции (и запроса) которая могла-бы прочитать эту запись.
  • при вставке записи, запись не видна до тех пор пока вставившая ее транзакция не будет завершена. Если в данный момент активны транзакции repeatableread, то они не увидят такую запись даже если был сделан commit для вставляемой записи, поскольку их transaction id меньше чем у транзакции, вставившей запись.
  • при изменении записи, пока транзакция не закончена, на свободное место страницы данных записываются только измененные поля (delta). Транзакция, изменившая запись, вынуждена производить "сборку" записи из старых и новых значений чтобы увидеть актуальные данные. Другие транзакции будут видеть старые версии записей, поэтому блокировок по чтению не возникает. Delta не может превышать 256 байт,  кроме этого изменения полей blob не сохраняются как delta, т.к. в записи хранится не содержимое blob а его идентификатор (изменения blob сохраняются на отдельных страницах, но не на целой странице на 1 blob, а сегментами). Если изменений много, то IB выделяет для их хранения  дополнительные страницы данных. При большом количестве изменений "сборка" записей может вызвать более интенсивный обмен с диском и общее ухудшение производительности.

    Возможна ситуация, при которой существует несколько версий одной записи, а не две как это кажется (старая и новая). Например транзакция 1 видит версию 1, транзакция 2 обновляет ее до версии 2, транзакция 3 видит версию 2, транзакция 2 подтверждает изменения но версия 2 остается видимой для транзакции 3, транзакция 4 обновляет запись до версии 3 и т.д.

    1.66 Какие блокировки обеспечивает IB ?

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

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

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

    1.67 Существуют-ли в IB блокировки по чтению ?

    Нет. Невозможно заблокировать запись чтением, и невозможно заблокировать запись от чтения. Запись можно блокировать от изменения или удаления только заблаговременным обновлением. Т.е. чтобы никто другой не мог сделать UPDATE записи, вы должны тоже выдать UPDATE этой-же записи ("холостой" UPDATE).

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

    SET TRANSACTION READ WRITE NO WAIT READ COMMITTED NO RECORD_VERSION.

    Такая транзакция не будет иметь возможности читать старые версии записей, если есть новые версии этих записей (режим NO RECORD_VERSION).
    Этот уровень изоляции ошибочно использовался в BDE вплоть до версии 4.01. В 4.01 и выше транзакция ReadCommitted имеет параметр RECORD_VERSION. Подробнее об этом см. статью.

    1.68 Какие уровни изоляции поддерживает IB ?

    Для начала заметим, что режим Dirty Read (чтение неподтвержденных записей) IB не поддерживает. Т.е. такая ситуация в механизме многоверсионности  записей просто невозможна.

    Поддерживаемые уровни:

  • Read Committed - чтение подтвержденных записей, то-же самое что и "невоспроизводимое чтение".
  • Repeatable Read- воспроизводимое чтение (snapshot). Видны только те версии записей, которые существовали на момент начала такой транзакции. Внутри такой транзакции разрешено не только чтение, но и вставка/удаление/модификация записей.
  • SNAPSHOT TABLE STABILITY - snapshot с резервированием таблиц. Таблицы могут быть заблокированы от изменения, но никогда - от чтения. (В Delphi этот режим включить невозможно, т.к. BDE не может управлять параметрами транзакций любого SQL-сервера. Через IB API - возможно)

    примечание: у транзакций IB есть большое количество дополнительных параметров, которые можно изменять только через IB API. При работе из BDE используются умолчательные значения этих параметров. Более подробно см. IB API Guide.
    примечание: в BDE 5.01 (www.borland.com/devsupport/bde) для IB появилась поддержка режима WAIT/NO WAIT и COMMIT/COMMIT RETAIN через параметры алиаса.

    Подробнее см. документ http://www.ibase.ru/devinfo/ibtrans.htm

    1.69 С какими транзакциями работают BDE, WISQL ... ?

    BDE как универсальный интерфейс доступа к данным работает с тремя типами транзакций - DirtyRead, ReadCommitted, RepeatableRead. Вместо DirtyRead для IB используется ReadCommitted.

    Вы можете стартовать любую из этих транзакций из Delphi, установив нужный уровень изоляции в свойстве TDatabase.Transisolation и вызвав метод TDatabase.StartTransaction.

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

    Delphi 1.0, версия BDE <= 2.5 RepeatableRead

    Delphi 2.0, версия BDE = 3.x ReadCommitted

    Delphi 3.0, версия BDE >= 4.0 RepeatableRead

    Для того чтобы изменить транзакцию по умолчанию (ReadCommitted) для BDE =3.x на RepeatableRead, вы должны изменить при помощи BDECFG значение параметра DRIVER FLAGS с пустого на 512 (см. справку BDECFG либо READLINK.TXT).
    В BDE версии 4.0 и выше для IB можно при помощи флагов управлять не только типом транзакции, но и способом ее завершения по умолчанию (COMMIT или COMMIT RETAIN), что позволяет избежать автоматического перечитывания записей при COMMIT в режиме BDE SHARED AUTOCOMMIT. Более подробно см. READLINK.TXT из поставки Delphi 3.0 C/S, или BDEREADME.TXT из BDE 5.01 update.

    WISQL по умолчанию стартует транзакцию SET TRANSACTION SNAPSHOT, т.е. RepeatableRead. В WISQL вы можете стартовать любую транзакцию IB (см. SET TRANSACTION в HELP или документации).

    1.70 Можно-ли управлять параметрами транзакций IB из BDE ?

    К сожалению, нет. Нет способа получить из BDE native transaction handle для IB, поэтому изменить характеристики транзакции IB или стартовать свою транзакцию через BDE невозможно.

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

    1.71 Что такое BLR ?

    Binary Language Representation. В таком виде хранятся и исполняются в IB триггеры, хранимые процедуры, контроль полей и prepared queries.

    Может-быть кому-то покажется странным, но язык SQL не является интерпретируемым. Например, имена полей в операторе SELECT могут быть обработаны только после разбора имен таблиц, участвующих в этом SELECT.

    1.72 Что такое UDF ?

    User Defined Functions - функции, определяемые пользователем. Вы можете на C или C++ (для любой платформы) или на Delphi 2.0 (для Windows NT) написать собственные функции и расширить таким образом функциональность IB.

    (исключением является IB for NetWare, т.к. для написания UDF пользователю пришлось-бы писать NLM. Кроме того, некорректная работа такого NLM могла-бы повлечь за собой "падение" сервера NetWare.)

    Существуют как свободно распространяемые библиотеки UDF, так и коммерческие (от MER Systems, более 100 функций).

    ДемоЦентр предлагает вам собственный бесплатный набор UDF, который больше представляет собой подробнейший "учебник" для написания собственных UDF на Delphi 2.0. Безусловно в этом наборе вы найдете множество полезных функций, таких как работа с датами, строками, varchar, поиск подстроки в BLOB и т.п.

    Также весьма популярна бесплатная библиотека FreeUDFLib, содержащая большое количество функций в исходных текстах (Delphi).
    см. раздел UDF в "Файлах и утилитах".

    1.73 Не восстанавливаются (restore) процедуры (sp) с QUERY PLAN. Что делать ?

    Это действительно ошибка в GBAK. Исправлено в IB 5.0.

    Тем кто продолжает работать на версиях 4.x, можно предложить следующее решение:
    перед backup выполнить скрипт, который заменит проблемные процедуры на пустые
    alter procedure xxx as begin suspend; end^
    ...
    А после restore выполнить скрипт, восстанавливающий текст процедур.

    1.74 Почему Server Manager не показывает Database Connections на WinNT ?

    Этот пункт меню "платформо-зависимый". Для NetWare и Local IB он разрешен, а для IB 4.0, 4.1 for NT - запрещен.

    Для IB 4.2 и 5.0 for NT этот пункт разрешен, поскольку с версии 4.2 IB имеет архитектуру SuperServer.

    примечание: версия IB 5.1 for RH Linux 5.2 имеет архитектуру Classic, и не позволяет просматривать количество активных соединений.

    1.75 Что такое архитектура SuperServer ?

    Исторически сложилось так, что первоначально SQL-серверы работали в архитектуре, когда подсоединение нового пользователя вызывало создание нового процесса SQL-сервера, работающего в отдельном адресном пространстве. Такие процессы могли общаться друг с другом только через Shared Memory или похожие механизмы.

    Постепенно большинство производителей SQL-серверов пришли к выводу, что такая архитектура устарела, и перешли на новую - SuperServer. Это громкое слово всего-лишь означает, что на каждого пользователя создается новый поток (thread) вместо процесса. Таким образом потоками можно управлять в одном адресном пространстве, можно оптимизировать кэширование считываемых разными потоками данных, и т.д. Однако понятно, что такая архитектура менее устойчива к сбоям отдельных потоков (это относится ко всем реализациям данной архитектуры).

    IB соответствует архитектуре SuperServer с версии 4.2, 5.x для Windows95/NT, 5.x для Solaris и HP-UX, 5.6 для SCO.

    На NetWare архитектура SuperServer была всегда.

    1.76 Соответствует-ли реализация SQL в IB стандарту ANSI и какому ?

    Реализация SQL в IB соответствует entry level ANSI SQL-92. Это означает, что все возможности, заявленные в entry level, реализованы в IB. Также в IB реализованы некоторые возможности более высокого уровня (например домены), но для соответствия intermediate или full level требуется полная реализация соответствующего уровня, чего пока нет не только в IB, но и практически в любых других SQL-серверах.

    1.77 Можно-ли поместить GDB-файл на другой компьютер ?

    Это возможно только для 16-разрядного Local IB. Для 32-разрядных версий IB это невозможно. Даже если-бы это и было возможно, то надежность такой системы была-бы весьма низка. Вообще возможность помещать GDB на файл-сервер была специально отключена для того, чтобы исключить возможность доступа к одному-и тому-же GDB с разных станций, использующих Local IB или с других операционных систем. IB, как и любой другой SQL-сервер, может обеспечивать корректную работу с БД только как сервер (т.е. самостоятельно предоставляя конкурентный доступ к БД нескольким пользователям одновременно), а не в режиме разделения файла в сети.

    1.78 В чем разница между OAT и OIT ? (старейшей активной и старейшей заинтересованной транзакцией).

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

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

    В файле БД существует набор страниц, называемых Transaction Inventory Pages (TIP), в которых хранится информация о всех стартовавших транзакциях. Каждая транзакция может находиться в четырех состояниях: activ (активная, committed (подтвержденная), rolled back (отмененная), in limbo ("зависшая" между фазами при двухфазном подтверждении транзакций - two phase commit).

    Состояние транзакций может быть изменено автоматически записью в соответствующую страницу TIP.

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

    Для контроля "стоимости" старта транзакции, IB имеет концепцию "заинтересованной" (interesting) и "скучающей" (boring) транзакций. Boring транзакция - это либо подтвержденная либо полностью завершенная транзакция. Заинтересованная транзакция - либо активная либо "зависшая". Поэтому для стартовавшей транзакции нет необходимости знать состояние всех транзакций в БД, а только те, которые были (или еще есть) после "старейшей заинтересованной" транзакции.

    Sweeping - это "домашняя уборка", которая трансформирует "погибшую" но заинтересованную транзакцию в "скучающую" транзакцию.

    Вообще процесс sweep - ничего более последовательного сканирования всех таблиц БД. Поскольку обнаружение "мертвой" версии записи приведет к "сбору мусора", sweep превращает все "старейшие заинтересованные транзакции в "скучающие" транзакции, снижая стоимость старта новой транзакции. Sweep никого не блокирует и требует очень мало ресурсов.

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

    Проблемы со sweep в общем относятся к не процессу sweep, а к тому что кто-то стартовал 20000 транзакций. Старт одной транзакции имеет среднюю стоимость (запись на диск, сканирование TIP, создание собственного блока и блокирование его lock manager-ом), но старт и подтверждение 20000 транзакций - очень высокую стоимость.

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

    примечание: максимально подробное описание этого вопроса вы найдете в статье.

    1.79 В чем разница между CHAR и VARCHAR ? Что лучше использовать ?

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

    Если вы собираетесь хранить строки длиной не более 40-50 символов, то лучше использовать CHAR. Более подробно см. статью.

    При передаче по сети в текущих версиях IB VARCHAR передается так-же как и CHAR (т.е. неэффективно). Исправлено в IB 5.0.

    1.80 Можно-ли поместить БД IB на CD-ROM ?

    В IB 4.x и 5.x база данных должна быть всегда доступна на запись, т.к. даже простая выборка (чтение) из БД стартует транзакцию, а информация о транзакции должна быть записана в БД.

    Возможность работать с read-only базами данных, а следовательно и помещать их на CD-ROM, появилась только в IB 6.0 и Firebird. Для того, чтобы сделать БД read-only, достаточно выполнить команду

    gfix database.gdb -mode read_only

    перед этим рекомендуется сделать backup/restore базе данных, предварительно установив флаг no_reserve в true (ключ -use у gfix), чтобы данные в БД были максимально уплотнены.
    Также возможно установить режим read_only прямо при restore соответствующей опцией gbak.

    Подробнее по режиму read_only см. документацию по IB6.

    1.81 Как выполнить create procedure/trigger при помощи TQuery?

    Для этого у TQuery нужно установить property ParamCheck:=False. При этом текст запроса не будет проверяться на наличие параметров, которые предваряются двоеточием (:). Никаких других изменений не требуется.

    1.82 Как установить генератор в нужное значение в процедуре или триггере?

    Действительно, оператор SET GENERATOR... является оператором DDL, а операторы DDL не допускаются в триггерах или процедурах. Установить значение генератора в нужное можно очень простым способом (например в 0):
    ...
    TMP=GEN_ID(MYGEN, -GEN_ID(MYGEN, 0));
    ...

    Т.е. увеличить значение генератора на его текущее отрицательное значение.

    1.83 Не могу подсоединиться к IB из под web-сервера IIS, Netscape, Baikonur и др.

    Если вы обращаетесь к IB из IIS, Baikonur и т.п. Web-серверов, то нужно использовать строку коннекта как для удаленного сервера, т.к. локальное подсоединение работать не будет. Например, 'server:c:\dir\data.gdb' или '\\server\c:\data.gdb'.

    1.84 Ошибка при установке "Internal error near IBcheck"

    Эта ошибка вызвана порчей ключа registry инсталлятором Delphi 4. Исправить ее можно, запустив RegEdit и проверив ключ HK_CURRENT_USER/Environment. Значение PATH должно быть строкового типа. Если это не так, то PATH надо поменять (или пересоздать) на строковое значение.


    (с) 1997 Epsylon Technologies