Обычно это называют FAQ или ЧАВО, однако здесь я решил поместить
может и не такие частые, зато весьма нетривиальные вопросы, над которыми пришлось поломать голову.
Q: Cталкивался ли кто-нибудь с ошибкой при создании индекса на строковые поля размером больше 84? A: Есть такое дело. Более подробно об этом и о работе с русскими буквами в IB можно узнать на www.ibase.ru. Как правило, проблема состоит в том, что требуется длинное поле и быстрый поиск по нему. Я выкрутился создав более короткое поле и индекс по этому короткому полю, (соответственно в запросе ставлю двойное условие: where name=?N1 and nameidx=?N2) : ALTER TABLE Wares ADD NAMEIDX VARCHAR(40) CHARACTER SET WIN1251; CREATE INDEX WareNameIdx ON WARES(NAMEIDX);/* Замечание от Д.Кузьменко: т.к. в этом поле все символы хранятся уже приведенными к UPPER, то COLLATE PXW_CYRL указывать не нужно, а значит, допуситмая длина поля NAMEIDX составит 252 символа */ SET TERM ^ ; CREATE TRIGGER Wares_NI_idx for Wares before insert position 0 AS BEGIN NEW.NAMEIDX=''; NEW.NAMEIDX=UPPER(NEW.NAME);/* вообще-то IB не позволяет обрезать строки просто так, здесь возникает exception, который перехватывается следующей строкой и подавляется (NAME varchar(512) collate pxw_cyrl, Waranty - просто любое другое поле), при этом кусок строки все равно присваивается. :-) Вероятно это баг сервера, но работает на всех известных версиях - идея Prenosil */ WHEN ANY DO New.Waranty=New.Waranty; END^ CREATE TRIGGER Wares_NU_idx for Wares before update position 0 AS BEGIN if ((NEW.NAMEIDX is null) or (New.Name!=Old.Name)) then BEGIN NEW.NAMEIDX=''; NEW.NAMEIDX=UPPER(NEW.NAME); WHEN ANY DO New.Waranty=New.Waranty; END END^ SET TERM ; ^ Другая идея (если всегда требуется только точное соответствие): использовать какое-нибудь хэширование, например, MD5 или DES, но тут уж без UDF не обойтись. P.S. В Firebird 1.0 появилась функция SUBSTRING(<string1> FROM <start> FOR <number>), позволяющая вырезать подстроку из строки, а в 2.х добавилась еще встроенная функция LEFT(<string>,<number>), возвращающая <number> первых символов строки, в результате вышеописанный трюк с WHEN уже не нужен :) Q: Как сделать так, что бы у двух пользователей, работающих с одной и той же таблицей, данные на экране отображались одинаковые? Т.е. один отредактировал (добавил), а у другой увидел. A: Это обсуждалось уже много раз и в разных конференциях. Общий вывод был такой:
Q: Можно ли подключить компоненты прямого доступа (например, FIBPlus) через соединение BDE ? A: Можно! Такой вопрос обычно возникает при миграции большого проекта с BDE на компоненты прямого доступа (IBX, FIBPlus, ...), когда не хочется держать два разных соединения с сервером, ресурсы которого не бесконечны. Делается это просто (например, для FIBPlus). Допустим, у нас есть DB1:TDatabase. Добавим на форму FibDB:TpDatabase, а в секцию USES модули BDE и IBase. После соединения компонента DB1 с базой данных (FibDB закрыт), выполняем команды: var h : tisc_db_handle; begin FibDB.DBName:='Cloned'; DBTables.Check(DbiGetProp(HDBIOBJ(DB1.Handle), dbNATIVEHNDL, @h, sizeof(tisc_db_handle), l)); FibDB.Handle:=h; end; Все, можно работать! Данный подход не имеет побочных эффектов и позволяет растянуть "переезд" на произвольное время. Q: Как перестроить/деактивировать индекс для внешнего ключа ? A: Идея от Alex Tischenko. В одной транзакции делаем 1) update rdb$indices set rdb$index_inactive = 3 where rdb$index_name = 'RDB$FOREIGN117' /* значение "3"-служебное, используется GBAK при restore */ 2) update rdb$indices set rdb$index_inactive = 0 where rdb$index_name = 'RDB$FOREIGN117' 3) commit;И через 3-4 мин огромный индекс перестраивается ! Неприятная особенность read_committed транзакции...
В Interbase версий 4.0-6.х и Firebird имеют особенность (на момент написания
заметки неисправленную), позволяющую при определенных условиях получить
неверные данные в read_committed транзакции. Особенность состоит в том, что
в read_committed транзакции версии записей проверяются
по общему TIP, а не по его статичной копии. В результате если при выполнении
SQL оператора какая-то из параллельных транзакций перейдет в состояние
commit, то часть данных в выборке будет учитывать изменения в этой транзакции,
а часть - нет. create table test (ID integer) и в одной задаче (app1) в read_commited транзакции: C:=0; P:=0; ibquery.sql.text:='Select count(*) from test'; While C<500000 Do Begin ibquery1.open; C:=ibquery1.Fields[0].asinteger; if C<>P then begin P:=C; RichEdit1.Lines.Add(IntToStr(P)); end; ibquery1.Close; end; а в другой (app2, в общем, не важно какая транзакция): j:=0; IBTransaction1.starttransaction; For I:=1 to 600000 do begin ibquery1.Params[0].asinteger:=I; ibquery1.execsql; inc(j); if j=30000 then begin j:=0; IBTransaction1.commit; IBTransaction1.starttransaction; end; end; IBTransaction1.commit; запускаем app1, app2 и ждём. В принципе объем можно уменьшить, у меня получилось
Q: При установке IB4.0 (а возможно и любого classic server) под Win2000
количество коннектов по TCP ограничено
цифрой в 110-120. По NetBEUI это число выше: около 170. При попытке
установления следующего коннекта, у клиента выскакивает сообщение:
A от Aleksey Karyakin: Необходимо в настройках сервиса ibremote поставить галку "Allow service to
interact with desktop". Проблема в размере non-interactive desktop heap, который по
умолчанию равен 512 К. Если сделать "Allow interact...",
то используется interactive desktop heap, который 3М.
Размеры heap-ов можно регулировать реестром, но сильно увеличивать тоже
нельзя, поскольку они статические и общий объем памяти под них тоже
ограничен. Подробно все описано на www.microsoft.com в: Различия в возвращаемых данных между IB5-6 и IB4 Про отличия в обработке данных IB5 и IB6 от предыдущих версий написано много, но 2 особенности почему-то забывают, а они тем временем доставляют ряд неприятностей при разработке клиентского приложения, работающего с разными версиями IB: Select Field1, (select sum(Field2) from T2 where T2.N=T1.N) from T1вернет разный тип второй колонки в IB 4.x (= varchar(127)) и более старших версиях (равен типу Field2). Для корректной работы датасета придется сделать CAST((select ...) as ...) и привести столбец к нужному типу; Тут уж ничего не поделаешь, придется подстраиваться под кривые наименования IB4: выполняем запрос в WISQL или еще какой-нибудь универсальной программе, запоминаем имена полей результата и жестко прописываем их в запросе через AS. Теперь IB5-6, честно отработав переименование, вернут ту же кривизну что и IB4. B-) SELECT NUM || IDENT FROM SomeTableпрекрасно отработает в старших версиях, но вернет ошибку "Cannot transliterate..." в IB4. Решение: сделать первое поле текстовым. Этого можно добиться, поменяв IDENT и NUM в запросе местами, добавить в начале какой-нибудь символ: SELECT 'A' || NUM || IDENT FROM SomeTableили просто сделав CAST(NUM as VARCHAR(...)). Q: Как сбросить / установить флаг NOT NULL у поля в уже заполненной базе данных? A: Такая проблема возникает при backup/restore когда к заполненной таблице было добавлено NOT NULL поле, тогда backup проходит нормально, а вот restore - нет. На www.ibase.ru есть специальная утилитка, позволяющая обнаружить такую ситуацию. Решается все очень просто: запрос update rdb$relation_fields set rdb$null_flag = null where rdb$relation_name = 'МОЯ_ТАБЛИЦА' and rdb$field_name = 'МОЕ_ПОЛЕ' and rdb$null_flag = 1;сбросит данный аттрибут. ('МОЯ_ТАБЛИЦА' и 'МОЕ_ПОЛЕ' должны быть набраны заглавными буквами, замечание: в третьем диалекте допускаются Case Sensitive названия) Q: А можно как-то сбросить NOT NULL флаг в бэкапе, если он не восстанавливается из-за этой ошибки, а исходная база не сохранилась?
A: Вообще говоря можно, но 100% результат я не гарантирую. Все нижеизложенное
вы делаете на свой страх и риск. Хотя, если манипуляции проводятся над
копией бэкапа, то никакого риска и нету Если NOT NULL стоит в домене, то сначала в бэкапе надо найти нужный домен по его имени,
а затем в HEX режиме искать последовательность
Если NOT NULL стоит в поле, то надо по имени найти в бэкапе нужную таблицу и нужное поле
В HEX режиме ищем ту же самую последовательность из 6 байт (она расположена
перед началом следующего поля): Сохраняем, пытаемся отресторить, если получилось - заполняем NULLы, ставим у поля снова NOT NULL и работаем дальше. Не получилось - увы, ищем другой метод.
P.S. Т.к. формат бекапа меняется от версии к версии, необходимо
проверять то что описано выше:
берем рабочую базу, делаем бэкап метаданных, ресторим,
ищем нужное поле, смотрим, где стоит NOT NULL:
у поля или у домена, и снимаем NOT NULL. P.P.S. При ремонте одного из бэкапов получилось так, что NULL-ы попали в поля первичного ключа, и при ресторе эти записи просто выкинулись. Мне повезло: это была второстепенная табличка, где потеря записей была не критична. Однако примите к сведению. P.P.P.S. Еще раз напомню: не делайте необратимых действий, все операции делайте над КОПИЕЙ бэкапа или базы. И только когда убедитесь, что все работает - только тогда можно подменять рабочую базу отремонтированной. Q: Какую конфигурацию сервера выбрать?
A: Сложный вопрос, есть много рекомендаций на этот счет, например: Приведу также конфигурацию своего сервера, которой я вполне доволен (активно работает около 100 юзеров, есть тяжелые запросы): CPU: 2x Pentium III 1000 (512kb, 133MHz)Коментарии: столь старая версия IB была выбрана только потому, что на момент установки это был единственный классик под Windows, а в дальнейшем я исходил из принципа "работает - и не трогай". Чем хорош классик - тем что завалить его целиком очень сложно, при падении отдельного клиента все остальные продолжают нормально работать. Как потом выяснилось, у классика также нормально распределяется нагрузка между двумя процессорами. Реально, 2Gb памяти оказалось многовато. IB выделяет и освобождает память динамически, поэтому 100 клиентов занимают в среднем около 700Mb памяти. Плюс память на OS, disk cache, т.е. 1-1.5Gb должно было хватить. Вторая сетевая карта также оказалась лишней: пиковая загрузка достигается только при копировании файла backup-а на другую машину, а так - не более 1 Mb/сек при средней около 200 Kb/сек. Удачной находкой оказалась батарейка для памяти RAID. Ее поисками я занялся после глюка UPS-a, оставившего от базы одни обломки. Теперь даже при грубом выдергивании шнура питания из сервера батарея удержит память в течение суток и при последующем включении все данные корректно запишутся на диск. Итак, лично мое мнение: при выборе "железа" не надо экономить на памяти и быстрой дисковой системе, остальное менее важно. Засада в Interbase под Linux: "караул! я могу удалить или переименовать файл работающей БД!" (по следам обсуждения на forums.demo.ru)
Ну, на самом деле это не засада, а просто особенность файловой системы
UNIX, о которой часто забывают пришедшие из DOS/Windows. Дело в том,
что файловая система и система каталогов в UNIX - суть вещи разные, т.е.
у файла нет имени как такового, а есть дескриптор inode, в котором
хранятся параметры файла. Система имен и каталогов - совершенно
отдельная структура, ссылающаяся на inode файлов. Соответственно ничто
не мешает файлу иметь несколько имен (называемых hard links),
эти имена совершенно равноправны, их можно добавлять и удалять,
и когда их количество станет равным нулю - тогда ОС удалит файл
физически. Если в этот момент файл был кем-то открыт, то удаление
произойдет после его закрытия. Маленькая особенность STARTING WITH Иногда требуется выбрать из таблицы строки, являющиеся подстрокой заданной строки, например, найти название города (скажем, 095) по заданному номеру телефона (0951234567). При этом не все догадываются, что обычный синтаксис CONTAINING или STARTING WITH можно вывернуть наизнанку: select * from Table1 where '0951234567' containing Field1 или select * from Table1 where '0951234567' starting with Field1однако в случае со STARTING WITH есть засада, когда поле Field1 - текстовое. Например, если в isc4.gdb выполнить запрос: select User_Name from Users where 'SYSDBA123' starting with USER_NAMEмы ничего не получим. Однако, если использовать CAST: select User_Name from Users where 'SYSDBA1234' starting with cast(USER_NAME as varchar(128))то все заработает, хотя USER_NAME и так имеет тип varchar(128). Наиболее вероятный источник этой загадки - неверное использование сервером индекса, т.к. в первом случае (без CAST) план запроса выглядит так: PLAN (USERS INDEX (USER_NAME_INDEX)), а во втором: PLAN (USERS NATURAL) Q: Можно ли сделать триггер на SELECT? A: Вообще говоря, нет. Однако есть хитрый способ, предложенный Prenosil:
Q: Как получить имена индексов для построения плана запроса? A: Вот таким запросом select rdb$index_name from rdb$relation_constraints where rdb$constraint_type=:Ttype and rdb$relation_name=:Ttableгде Ttype - тип индекса, например, 'PRIMARY KEY', Ttable - имя таблицы (uppercase), например, 'MYTABLE' (замечание: в третьем диалекте допускаются Case Sensitive названия таблиц) Q: При добавлении в таблицу новой записи, в которой есть Foreign Key, если запись в Master таблице с таким ключом в этот момент редактируется, я получаю отлуп: lock conflict on no wait transaction. violation of FOREIGN KEY constraint "INTEG_332" on table "MYTABLE" A: Это особенность реализации FK в Interbase/Firebird (на данный момент это версии 7 и 1.5 соответственно). Чтобы избежать таких ситуаций, необходимо часто редактируемые поля (например, хранящие агрегаты) вынести в отдельную таблицу и организовать связь с исходной таблицей 1-к-1. Если это невозможно по каким-то причинам, то рекомендуется использовать CachedUpdates и максимально короткие пишущие транзакции, это позволит свести такие конфликты к минимуму. Q: Как сделать, чтоб триггер не срабатывал при вставке какой-то записи? Сделать его inactive, а затем active в той же транзакции не получается... A: Менять метаданные в ходе нормальной работы - это как минимум
дурной тон. Варианты: Q: Как задать тип данных для вычисляемого поля? A: Обычно этого делать не требуется, т.к. тип результата определяется по используемым данным.
Однако бывают накладки, например, если ваш код должен одинаково работать как в 1м, так и в 3м диалекте. DURATION COMPUTED BY (TIME_END-TIME_START)в 1м диалекте будет типа DOUBLE PRECISION, а в 3м - BIGINT. Чтобы этого не происходило, используйте CAST: DURATION COMPUTED BY (CAST(TIME_END-TIME_START AS double precision)) Кстати, аналогичный трюк можно использовать для указания типа данных во VIEW. (c) Konstantin Beliaev, 2001-2011 |
Программирование > Interbase/Firebird >