В этой статье я расскажу вам о том, как всерьез задумался об альтернативе Oracle. А как же Postgre, скажете вы? Да, но есть нюансы. Сперва разберемся с вопросом «Почему Oracle?».
Бизнес логика у нас в БД. В книге Oracle для профессионалов Том Кайт пишет
При разработке приложений баз данных я использую очень простую мантру:
если можно, сделай это с помощью одного оператора SQL;
если это нельзя сделать с помощью одного оператора SQL, сделай это в PL/SQL;
если это нельзя сделать в PL/SQL, попытайся использовать хранимую процедуру на языке Java;
если это нельзя сделать в Java, сделай это в виде внешней процедуры на языке C;
если это нельзя реализовать в виде внешней процедуры на языке C, надо серьезно подумать, зачем это вообще делать...
и в проектировании систем я следую этому правилу. Особенно радуют объектные типы в Oracle, с их помощью сложная бизнес логика красиво и удобно реализуется по всем канонам ООП.
Oracle стоит дорого. Купить его и не использовать все, что в нем есть, будет ошибкой.
И еще, всегда есть фактор команды и компетенций. Если у вас команда десять лет разрабатывает все в Oracle, переучиваться на Postgre может быть болезненно.
Oracle стоит дорого. Настолько дорого, что об этом можно написать несколько раз, и не задумываться о необходимости Oracle в новом проекте будет ошибкой.
Уже несколько раз мне попадались публикации про корейский продукт Tibero, якобы создаваемый для замены Oracle. А нынче у них аттракцион невиданной щедрости — лицензии на Standard раздают для разработчиков практически бесплатно, за доллар на сокет. Итак, разбираемся: что на данный момент могут предложить корейцы. С автомобилями ведь у них, уже (почти) получилось!
Описание эксперимента
Представители TMaxSoft говорят, что Tibero почти на 100% совместима с Oracle, и есть утилита для миграции базы. Я решил взять свою базу продукта, с бизнес логикой в Oracle, с использованием всей прелести ООП в PLSQL, и перенести это в Tibero. В данной публикации мы не рассматриваем миграцию самих данных, это менее интересно, и я пока не пробовал полноценный перенос.
Задача такая:
1. Перенос таблиц.
2. Перенос индексов, ключей и т.п.
3. Перенос пакетов и триггеров.
4. Перенос объектных типов.
Но сперва, разберемся с инструментами.
Инструменты
Гугл мало знает про Tibero. Саму базу мы скачали в виде виртуальной машины, на которой все уже развернуто. Инструмента всего два: утилита для миграции T-UP, и IDE для DBA и разработчика tbAdmin. Сделано все на Java, запускается где угодно, в теории.
T-UP выглядит примерно так:
Главное окно: коннекты к базам.
Если нажать на Options, можно кое-что настроить.
Самое главное — можно выбрать типы объектов для миграции.
Никакой инструкции, помощи, подсказок обнаружить не удалось.
Производит впечатление самодельной утилиты. Иногда она вылетала с ексепшенами, но в основном работала как надо.
Второй инструмент IDE Tibero Admin. Ее можно скачать с сайта TMaxSoft, если предварительно там зарегистрироваться. Там же можно получить демо лицензию.
Tibero Admin выглядит как типичная старенькая IDE
Я привык к отличному инструменту PL/SQL Developer от Allround Automations. В Tibero Admin забудьте о контекстных подсказках, ничего не появится у вас на экране после нажатия «точечки», она не будет вам дописывать названия таблиц и объектов. Просто печатайте код, вы ж программисты. Помощь, документация? Нет. Есть документация по СУБД на сайте производителя, интересная такая… без поиска. По IDE документации не обнаружено. Впрочем, там ничего сложного. Проблемы были с авторизацией — оказалось, пользователю нужно дать права DBA, чтоб просто зайти в tbAdmin. И интересно с портами, 8630 это для SYS, для всех прочих 8629.
IDE подглючивает. Время от времени, когда куда-нибудь ткнешь, вылетают сообщение index out of bounds, очень пугает сообщение типа java.lang.Exception: commitment Succeeded. Надо учитывать разные типы окон SQL и PSM: В первом вряд ли скомпилируешь программный код, во втором — не выполнишь запрос. После PL/SQL Developer — жалкое подобие левой руки…
Приступаем к эксперименту.
Перенос таблиц
Выбираем в T-UP схему, нажимаем Migrate, предварительно в опциях выбрав «таблицы» и начинается процесс переноса. Я столкнулся с двумя проблемами.
TABLESPACES. Я их решил перенести до таблиц, но Tibero, похоже, попытался зарезервировать под них сразу столько места, сколько они занимают в исходной базе. А это очень много, и он не смог. Табличные пространства я создал вручную, и дальше все пошло отлично.
Кроме таблиц с DEFAULT значениями дат типа '31.12.2019'. В настройках Oracle у нас данный формат прописан, для Tibero помогает
alter session set nls_date_format='DD.MM.YYYY';
, но для T-UP такое сделать негде. Коллеги из TMaxSoft посоветовали установить переменную TB_NLS_DATE_FORMAT=«DD.MM.YYYY », но лично мне не помогло. Возможно, я что-то не так сделал. Пришлось таблицы с такими параметрами создать вручную, их не так много.
Итог первого шага: перенос структуры таблиц из Oracle в Tibero работает хорошо.
Ключи и индексы.
Выбираем галочки INDEX, CONSTRAINT в T-UP, и вперед.Проблемы возникли с CHECK из-за той же самой ситуации с датой. В целом индексы, первичные ключи, чеки — создались. А вот внешние ключи в свежесозданной базе я обнаружить не смог. И миграция констреинтов заканчивается в логе T-UP сообщением «Migration Failed: java.lang.NullPointerException». Совпадение? Не думаю…
Пакеты и триггеры.
В триггерах у меня ничего сложного нет, они создались прекрасно. Как работают, правда, не проверял, это отдельная история будет.
Поговорим о процедурах и функциях, реализующих часть логики. Тут, к сожалению, не все прошло идеально.
Из простого: в армии Tibero нет слова NEW. Конструкция o := NEW t_my_type() не скомпилируется. В Oracle оно, правда, не обязательное, но я всегда писал зачем-то. Пришлось удалять.
Роль DBA позволяет в SQL Window иметь доступ ко всем таблицам. Однако, при компиляции пакета или процедуры в схеме с такой ролью, при использовании таблиц и объектов другой схемы, нужно дать соответствующий grant на объект. Магия роли DBA тут не помогает.
Из специфического. Попался мне странный FORALL в моей базе, который, однако, работает.
-- Пример упрощен для заметки
PROCEDURE save_tar_test AS
TYPE number_table IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
TYPE date_table IS TABLE OF DATE INDEX BY BINARY_INTEGER;
TYPE varchar50_table IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
TYPE char1_table IS TABLE OF CHAR(1) INDEX BY BINARY_INTEGER;
TYPE t_tar_det_out_upd_rec_test IS RECORD
(
-- доп параметры
cmr_tar_det_id number_table,
in_stamp date_table,
err_code_id number_table
);
tdou_rec t_tar_det_out_upd_rec_test;
BEGIN
-- Это тестовый пример, тут tdou_rec не заполнен: но такой код компилируется в Oracle
FORALL j IN 1 .. tdou_rec.cmr_tar_det_id.COUNT
UPDATE cmr_tar_det dd
SET err_code_id = tdou_rec.err_code_id(j)
WHERE dd.cmr_tar_det_id = tdou_rec.cmr_tar_det_id(j)
AND in_stamp = tdou_rec.in_stamp(j);
END;
Tibero говорит «dml statement must have bulk-in parameter i a forall close». И я его понимаю в глубине души, но этот код пришлось переделать на обычный цикл.
Хуже ситуация с таблицами, содержащими объекты.
CREATE OR REPLACE TYPE S1.TYPE_PAY_HIST AS OBJECT
(
pay_status_id NUMBER(1),
stamp DATE
);
CREATE OR REPLACE TYPE S1.TABLE_PAY_HIST AS VARRAY(10) OF type_pay_hist;
create table S2.RECEIPT
(
receipt_id NUMBER(8) not null,
pay_sum NUMBER(12,2) not null,
receipt_hist S1.TABLE_PAY_HIST
);
FUNCTION receipt_status_change(p_cmr_receipt_id NUMBER, p_new_status NUMBER) RETURN NUMBER
AS
l_receipt_hist s1.table_pay_hist;
l_type_pay_hist s1.type_pay_hist;
l_status NUMBER;
BEGIN
BEGIN
SELECT receipt_hist
INTO l_receipt_hist
FROM receipt p
WHERE p.receipt_id = p_cmr_receipt_id
FOR UPDATE;
EXCEPTION WHEN NO_DATA_FOUND THEN
RETURN c_err_not_find_status;
END;
...
END;
Имеем ошибку при компиляции, type mistmatch.
Ребята из TMaxSoft предложили такой вариант кода:
create or replace FUNCTION ....
AS
l_receipt_hist table_pay_hist;
l_type_pay_hist type_pay_hist;
l_status NUMBER;
cmr_receipt_row cmr_receipt%rowtype;
BEGIN
BEGIN
SELECT * INTO cmr_receipt_row
FROM cmr_receipt p
WHERE p.cmr_receipt_id = p_cmr_receipt_id
FOR UPDATE;
SELECT
TYPE_PAY_HIST(r.pay_status_id,r.stamp)
bulk collect INTO l_receipt_hist
FROM cmr_receipt p,table(p.receipt_hist) r
WHERE p.cmr_receipt_id = p_cmr_receipt_id;
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('NO_DATA_FOUND');
RETURN null;--c_err_not_find_status;
END;
...
END;
Работать оно, наверное, работает. Но не так удобно, и придется переделывать код.
Остальное — скомпилировалось нормально, если не считать специфические вещи типа SDO_GEOM. И, кстати, в Tibero есть аналог. До его изучения руки еще не дошли.
Типы
В одном из проектов мы используем мощь ООП by Oracle на полую катушку.
Самый волнующий вопрос к Tibero был именно про это.
Создаем некий тип, являющийся базовым для набора других типов.
CREATE OR REPLACE TYPE t_tar_object AS OBJECT
(
id NUMBER(12),
smth NUMBER(12),
CONSTRUCTOR FUNCTION t_tar_object RETURN SELF AS RESULT,
MEMBER FUNCTION target(param IN NUMBER DEFAULT NULL) RETURN NUMBER,
MEMBER FUNCTION inside(o t_tar_object) RETURN NUMBER,
MEMBER FUNCTION clone RETURN t_tar_object
) NOT FINAL;
-- Тело
CREATE OR REPLACE TYPE BODY t_tar_object AS
CONSTRUCTOR FUNCTION t_tar_object RETURN SELF AS RESULT
AS
BEGIN
RETURN;
END;
MEMBER FUNCTION target(param IN NUMBER DEFAULT NULL) RETURN NUMBER
AS
BEGIN
RETURN id;
END;
MEMBER FUNCTION clone RETURN t_tar_object
AS
BEGIN
RETURN NULL ;
END;
END;
И теперь пробуем создать ему достойного наследника.
CREATE OR REPLACE TYPE t_tar_service UNDER t_tar_object
(
is_virtual NUMBER(1),
CONSTRUCTOR FUNCTION t_tar_service (p_serv_obj t_tar_object, p_main_id NUMBER, p_pack_id NUMBER) RETURN SELF AS RESULT,
OVERRIDING MEMBER FUNCTION target(param IN NUMBER DEFAULT NULL) RETURN NUMBER,
OVERRIDING MEMBER FUNCTION clone RETURN t_tar_object
) NOT FINAL;
Компилятор откажется переопределять функцию target. Вопросов к функции clone у него, при этом, не возникает. Выяснилось, что Tibero не нравится OVERRIDING, когда у метода есть параметры. Ладно, убираем слово OVERRIDING. Но это настораживает, и пишем проверочные скрипты. Пока с родительским типом.
declare
o1 t_tar_object;
i1 number := 100;
begin
o1 := t_tar_object;
o1.id := 1;
i1 := o1.target;
-- dbms_output.put_line (i1) падает с ексепшеном.
-- но нас так просто не смутить, пытаемся выяснить, что же находится в i1 после этих
--манипуляций
if i1 > 0 then
dbms_output.put_line ('big');
end if;
if i1 < 0 then
dbms_output.put_line ('small');
end if;
if i1 = 0 then
dbms_output.put_line ('zero');
end if;
if i1 is null then
dbms_output.put_line ('null');
end if;
end;
Вроде мы не оставили выбора программе, какое бы значение не приняла переменная i1, что-то должно появиться в output. Но… ничего не появляется!
Сразу вспомнил отличный фильм
На этом странности не кончаются. Усугубляем эксперимент
declare
o1 t_tar_object;
i1 number := 100;
begin
o1 := t_tar_object;
o1.id := 1;
i1 := o1.target;
-- Не важно, что было раньше в переменной - зададим ей значение
i1 := 2;
if i1 > 0 then
dbms_output.put_line ('big');
end if;
if i1 < 0 then
dbms_output.put_line ('small');
end if;
if i1 = 0 then
dbms_output.put_line ('zero');
end if;
if i1 is null then
dbms_output.put_line ('null');
end if;
end;
Даже то, что после всех манипуляций с методами объекта, мы жестко задали значение переменной, ничего не меняет — output пустой.
Контрольный эксперимент, проверить себя на маразм:
declare
o1 t_tar_object;
i1 number ;
begin
o1 := t_tar_object;
o1.id := 1;
--i1 := o1.target; закомментируем эту строчку
i1 := 2;
if i1 > 0 then
dbms_output.put_line ('big');
end if;
if i1 < 0 then
dbms_output.put_line ('small');
end if;
if i1 = 0 then
dbms_output.put_line ('zero');
end if;
if i1 is null then
dbms_output.put_line ('null');
end if;
end;
В аутпуте появляется заветное «big». Жутковато, не правда ли? Методом научного тыка ребята из TMaxSoft выяснили, что если убрать из спецификации t_tar_object нюанс «NOT FINAL», то объект будет вести себя адекватно. Но зачем он тогда вообще нужен такой… без наследников.
Дальше про ООП в Tibero рассказывать смысла нет. Как и, собственно, ООП в Tibero. После этого возникает и другой вопрос: а тот код процедур и функций, который скомпилировался — работает ли корректно? Я пока не знаю. Смигрировалось и скомпилировалось огромное количество кода. Для проекта, не содержащего вышеописанных упражнений с типами, это однозначный успех. Но тестировать правильность исполнения кода — серьезная задача. И честно говоря, я не ожидал, что с ней столкнусь. Будет ли у меня проект на этой СУБД, пока сказать не готов. Но если и будет, то разработка будет происходить на Oracle, с использованием нормальных удобных инструментов, и с регулярной миграцией на Tibero. Писать код в IDE без контекстных подсказок и с периодическими ошибками самого интерфейса — удовольствие ниже среднего.
Выводы
Есть ли будущее у Tibero в его нынешнем состоянии? Не уверен. Ведь если поискать стоимость лицензий без учета мегаскидки, то один сокет Standard стоит порядка 800 000. Что дешевле чем Oracle, но не в разы. А как я убедился на своем опыте, пока что это даже рядом не Oracle.
Есть ли смысл использовать почти бесплатный Tibero, который предлагают сейчас? Возможно, да. Говорят, при оплате стоимости технической поддержки (99000 рублей в год на сокет), разрешается использовать эту базу в коммерческих проектах. Если у вас имеется в наличии команда ораклистов, и надо что-то не слишком мудреное создать и разместить на своем сервере, да подешевле и побыстрее — интересный вариант. Можно еще разыгрывать карту санкций, заявляя беспокойным заказчикам, что Корея это не США.
Переводить ли действующие проекты с Oracle на Tibero? Ни за что. Нет ни одной причины искать себе такие приключения. Проще отказаться от технической поддержки Oracle, и никому ничего не платить.
А если надо создать новый инстанс старого проекта, работающего на Oracle? И как следствие, покупать новые лицензии? А вот здесь подумайте. Не исключено, что ваша база качественно смигрируется и будет работать. Но сразу задумайтесь о поддержке двух направлений в проекте, либо переводить все на Tibero. Считаем трудозатраты, риски, выгоду от более дешевых лицензий. Сравниваем, решаем.
Быть может, в следующей версии Tibero все будет иначе. Надо лишь допилить типы, сделать нормальную IDE, может быть изменить ценовую политику. И если с СУБД будет как с автомобилями, то лет через 5 корейцы могут занять существенную долю рынка и потеснить гегемона. Будем посмотреть.
P.S.
Серьезным плюсом при выборе СУБД для нового проекта может стать работа тех.поддержки TMaxSoft. Я ведь даже еще лицензии за один доллар не купил, а ребята мне отвечали очень оперативно, явно заинтересованно. Помогали как с глупыми вопросами, так и с теми, что описаны здесь. Обратная связь отличная.
Автор: maep