ADG Оставить заявку
Блог Данные и аналитика 5 мин чтения

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-идиомы, надо переписывать, а не адаптировать. Тем, кто планирует такой переход, рекомендуем закладывать на хранимки минимум вдвое больше времени, чем кажется разумным на старте.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.