Tantor SE 17 в продуктиве: карта несовместимостей при миграции с MS SQL
Перевели аналитическую БД клиента с MS SQL Server на Tantor SE 17. Фиксируем карту несовместимостей T-SQL, что пришлось переписать в хранимках и где споткнулись.
Tantor SE 17 входит в продуктовую линейку как основная отечественная СУБД для КИИ в 2025 году
Несколько недель назад мы завершили основную фазу миграции аналитической базы данных одного из клиентов с MS SQL Server на Tantor SE 17. Клиент - субъект КИИ, значимый объект второй категории, дедлайн по переходу на отечественный стек уже прошёл, и держать лицензионный Microsoft SQL в продуктиве дальше было нельзя ни формально, ни практически.
Выбор в пользу Tantor SE, а не Postgres Pro Enterprise, был осознанным: на тендере Tantor выиграл по цене и по позиции вендора на переговорах. Для нас это тоже был первый крупный продакшн именно на Tantor SE 17 - до этого смотрели на него в тестовых стендах и в сравнительных обзорах вроде январского поста про Postgres Pro 17. Теперь есть что рассказать предметнее.
Контекст миграции
База - аналитический склад данных на MS SQL Server 2019, около 200 таблиц, плюс порядка 80 хранимых процедур на T-SQL. Нагрузка смешанная: ежедневные ETL-процессы и аналитические запросы с агрегацией за большие периоды. Не гигантская база, но хранимки писались лет семь разными людьми и содержали всё, что полагается в таких случаях: временные таблицы, курсоры, динамический SQL, несколько мест с NOLOCK и пара мест с CROSS APPLY.
Инструментарий для миграции аналитических платформ у нас отработан, но T-SQL в PostgreSQL - это отдельная история, которую лучше знать заранее, а не открывать по ходу.
Карта несовместимостей: что нашли
Разбиваем по зонам боли - от частого к редкому.
TOP N vs LIMIT. Самое массовое. В T-SQL SELECT TOP 100 ... - базовый приём, в PostgreSQL нет TOP, только LIMIT. Найти и заменить несложно, но TOP в хранимках с динамическим SQL - это отдельный квест, потому что строка с запросом склеивается в рантайме и статический поиск её не поймает. Потратили время на просмотр всего динамического SQL вручную.
ISNULL vs COALESCE. ISNULL(expr, default) - T-SQL специфика, в PostgreSQL не существует. COALESCE семантически эквивалентен для двух аргументов и работает везде. Замена механическая, но встречается часто.
GETDATE() vs NOW() / CURRENT_TIMESTAMP. Тоже массовое. GETDATE() в PostgreSQL не определена. Меняется на NOW() или CURRENT_TIMESTAMP - они идентичны для большинства случаев. Но есть тонкость: в T-SQL GETDATE() внутри транзакции каждый раз возвращает новое время, в PostgreSQL NOW() фиксируется на момент начала транзакции. В аналитических хранимках это обычно не критично, но знать надо.
CONVERT и CAST с форматами дат. CONVERT(VARCHAR, datecolumn, 104) - немецкий формат даты - в PostgreSQL не существует. PostgreSQL использует TO_CHAR(datecolumn, 'DD.MM.YYYY'). Форматных строк было много, и они отличаются синтаксически. Пришлось пройти по каждой вручную.
Временные таблицы #temp и ##globaltemp. В T-SQL # и ## - это синтаксис временных таблиц. В PostgreSQL временные таблицы создаются через CREATE TEMP TABLE. Функционально аналог есть, синтаксис принципиально другой. Хранимки, которые активно юзали #temp для промежуточных вычислений, переписывались с нуля - не правились, а именно переписывались, потому что логика была заточена под T-SQL-паттерн.
CROSS APPLY и OUTER APPLY. В PostgreSQL аналог - LATERAL JOIN. Синтаксически другое, но семантически то же самое. Два места в коде, оба переписаны без потери логики.
NOLOCK. В T-SQL WITH (NOLOCK) - хинт для грязного чтения. В PostgreSQL этого хинта нет, а уровень изоляции управляется иначе. Там, где NOLOCK стоял для производительности на аналитических запросах (а именно так - везде), просто убрали хинт. PostgreSQL по умолчанию не блокирует читателей при записи, так что проблема в основном решается сама.
Строковые функции. CHARINDEX - это STRPOS в PostgreSQL. STUFF - функция вставки подстроки - в PostgreSQL не существует, заменяется через OVERLAY или конкатенацию. LEN - это LENGTH. Немного, но в хранимках с манипуляциями со строками встречается регулярно.
Что оказалось неожиданным
Ожидали больше проблем с типами данных - в итоге тут было относительно спокойно. NVARCHAR в PostgreSQL нет, но VARCHAR с UTF-8 ведёт себя идентично для всего, что было в базе. DATETIME в PostgreSQL - TIMESTAMP, и это работает без сюрпризов.
Неожиданно болезненной оказалась работа с курсорами. В T-SQL курсоры - привычный инструмент для построчной обработки в хранимках. В PostgreSQL они тоже есть, но синтаксис другой и их использование в функциях выглядит иначе. Несколько хранимок с курсорами в итоге переписали в set-based логику - это заняло время, но результат лучше: запросы стали быстрее.
Tantor SE 17 специфичных сюрпризов по сравнению с ванильным PostgreSQL 17 не преподнёс. Это скорее хорошая новость: документация PostgreSQL применима напрямую, совместимость с расширениями стандартная.
Статус сейчас
ETL-процессы в продуктиве, аналитические запросы работают. Несколько хранимок ещё в доработке - там сложная логика с курсорами, которую решили переписать правильно, а не быстро. Производительность на аналитических запросах сравнима с MS SQL, на некоторых агрегациях - лучше, на паре сложных запросов планировщик выбирает не оптимальный путь и мы ещё разбираемся с хинтами и статистикой.
Вывод простой: миграция с MS SQL на Tantor SE (или любой PostgreSQL-дистрибутив) - это не конвертация скриптов, это переработка логики. Хранимки, написанные под T-SQL-идиомы, надо переписывать, а не адаптировать. Тем, кто планирует такой переход, рекомендуем закладывать на хранимки минимум вдвое больше времени, чем кажется разумным на старте.
- Postgres Pro Enterprise 17: сертификат ФСТЭК, реестр и сравнение с Tantor SE · 9 января 2025
- КИИ после 1 января: первый рабочий день и первые выводы · 6 января 2025