Базы данных Microsoft SQL Server и Oracle: корпоративный мир, лицензии, особенности
0%

Microsoft SQL Server и Oracle: корпоративный мир, лицензии, особенности

Microsoft SQL Server и Oracle: корпоративный мир, лицензии, особенности

Если предыдущие статьи трека были про движки, которые вы просто скачиваете и запускаете, то здесь всё иначе. Oracle Database и Microsoft SQL Server — это не только СУБД. Это юридические конструкции, вокруг которых выстроены отделы закупок, аудиторы вендора, сертифицированные администраторы и контракты на десять лет. Инженер, который приходит в такую систему с привычками из мира PostgreSQL, обычно ломается дважды: первый раз — об архитектурные отличия, второй раз — об счёт.

Эта статья про оба перелома. Мы разберём, как эти движки устроены внутри (и почему устроены именно так), что в них действительно лучше открытых аналогов, где спрятаны деньги, и какие аварии случаются в проде чаще всего.

Базовые понятия — реляционная модель, транзакции, индексы — предполагаются известными: см. https://courses.digitable.life/post/databases/01-relational-model/ и https://courses.digitable.life/post/databases/07-transactions-and-isolation/.

Зачем вообще существует «корпоративная СУБД»

Начнём с честного ответа на вопрос, который у любого разработчика в 2026 году висит в воздухе: зачем платить миллион долларов за то, что PostgreSQL делает бесплатно?

Ответ состоит из четырёх частей, и только одна из них техническая.

  1. Ответственность. Когда банковская АБС падает в 3 часа ночи, кто-то должен взять трубку и иметь контрактное обязательство починить. Open source-подписка (EDB, Percona) это тоже даёт, но у Oracle за спиной сорок лет судебной практики и регуляторного признания.
  2. Регуляторика и сертификация. Промышленное ПО — SAP, Siebel, банковские ядра, MES-системы на заводах — сертифицировано под конкретные версии конкретных СУБД. Вы не «выбираете БД», вы получаете её в комплекте.
  3. Интегрированный стек. Oracle продаёт связку Exadata + RAC + Data Guard + GoldenGate, где всё протестировано друг с другом. Microsoft продаёт связку SQL Server + SSIS + SSAS + SSRS + Power BI + Active Directory, где аутентификация и BI «просто работают».
  4. Технические возможности, которых в open source действительно нет или они моложе. Их меньше, чем говорят продавцы, но они есть: Cache Fusion в RAC, Automatic Storage Management, зрелый columnstore внутри OLTP-движка SQL Server, In-Memory OLTP без блокировок.

Ключевая мысль, которую стоит унести: корпоративная СУБД покупается не за скорость, а за снижение риска и за экосистему. Если ваш аргумент за Oracle звучит как «он быстрее» — вы почти наверняка ошибаетесь и переплачиваете в 20 раз.

Два мира на одной карте

Из таймлайна видно две разные стратегии. Oracle всю жизнь достраивал вертикальный монолит: одна БД, которая умеет всё, продаваемая по частям как опции. Microsoft строил горизонтальную платформу: сравнительно простое ядро, плотно вшитое в Windows-экосистему и BI-стек, продаваемое изданиями целиком.

Это отличие объясняет почти все практические различия дальше, включая ценообразование.

Oracle Database: как он устроен

Instance и database — разные вещи

Главное понятие, на котором спотыкаются новички: в Oracle instance (набор процессов и разделяемой памяти) и database (набор файлов на диске) — это разные сущности с разным жизненным циклом. Один instance может быть поднят над одной database; в Real Application Clusters несколько instance на разных серверах работают над одной и той же database на общем хранилище.

Архитектура Oracle instance и database

Разберём ключевые элементы.

SGA (System Global Area) — разделяемая память. Внутри:

  • Buffer Cache — кэш блоков данных, аналог shared_buffers в PostgreSQL. Грязные блоки сбрасывает DBWn.
  • Shared Pool — здесь живёт library cache с разобранными SQL-курсорами и планами, и row cache со словарём данных. Это принципиальное отличие от PostgreSQL: планы кэшируются глобально между сессиями, поэтому одинаковый текст запроса — это дешёвый soft parse, а новый текст — дорогой hard parse.
  • Redo Log Buffer — буфер журнала изменений, который LGWR сбрасывает в online redo logs при коммите.

PGA (Program Global Area) — приватная память сессии: рабочие области сортировок, hash join, bitmap-операций. Управляется параметром PGA_AGGREGATE_TARGET; при нехватке операция «спиллится» в temporary tablespace, и время выполнения растёт в разы.

UNDO tablespace — вот это самое интересное. Oracle реализует MVCC не так, как PostgreSQL: старые версии строк не остаются в самой таблице, а копируются в отдельные undo-сегменты. Плюс — таблицы не «раздуваются», нет VACUUM. Минус — undo конечен, и если долгий запрос попросит версию, которую уже перезатёрли, вы получите легендарную ORA-01555: snapshot too old.

Consistency через SCN

Каждое изменение получает SCN (System Change Number) — монотонный логический таймстемп. Запрос запоминает SCN на старте и при чтении блока сравнивает: если блок изменён позже, Oracle реконструирует его прошлое состояние из undo. Это даёт консистентное чтение без блокировок читателей.

Побочный подарок — Flashback Query, машина времени из коробки:

-- Что было в таблице 15 минут назад (пока undo не перезатёрт)
SELECT account_id, balance
  FROM accounts AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '15' MINUTE)
 WHERE account_id = 42;

-- Полная история изменений строки (требует Flashback Data Archive)
SELECT versions_startscn, versions_operation, balance
  FROM accounts VERSIONS BETWEEN TIMESTAMP
       SYSTIMESTAMP - INTERVAL '1' HOUR AND SYSTIMESTAMP
 WHERE account_id = 42;

Восстановить случайно удалённые данные без разворачивания бэкапа — это тот случай, где Oracle реально экономит часы простоя.

Multitenant: CDB и PDB

С версии 12c Oracle разделяет container database (CDB) и pluggable databases (PDB). CDB держит общий словарь и фоновые процессы; PDB — это независимая логическая БД, которую можно отцепить и подключить к другому CDB командой. Консолидация 50 маленьких баз в один CDB экономит память и лицензии.

-- Клонирование PDB для тестового окружения занимает секунды
CREATE PLUGGABLE DATABASE billing_test FROM billing_prod
  FILE_NAME_CONVERT = ('/oradata/billing_prod/', '/oradata/billing_test/');

ALTER PLUGGABLE DATABASE billing_test OPEN;

Подвох: до 19c бесплатно разрешалось три PDB на CDB, дальше нужна опция Multitenant. В 21c/23ai лимит подняли до 3 без опции и до 252 с ней — проверяйте по своей версии, это ровно та деталь, на которой ловят аудиторы.

Высокая доступность: RAC, Data Guard, ASM

  • RAC решает задачу отказоустойчивости инстанса и горизонтального масштабирования нагрузки. Узлы обмениваются блоками по interconnect (Cache Fusion), а не читают их заново с диска. Работает отлично для нагрузок с локальностью данных; ужасно — для «горячего» блока, который правят все узлы (классическая деградация на счётчиках и sequence без CACHE).
  • Data Guard — это репликация через redo-поток на отдельную копию БД, обычно в другом ЦОД. Защищает от потери хранилища. Режимы: Maximum Performance (асинхронно), Maximum Availability, Maximum Protection (коммит ждёт standby).
  • Active Data Guard — платная опция, позволяющая читать со standby, пока он применяет redo. Это ровно та функция, которую в PostgreSQL называют hot standby и отдают бесплатно.
  • ASM — собственный менеджер томов с зеркалированием и ребалансировкой, заменяет LVM/файловую систему.

PL/SQL

PL/SQL — полноценный процедурный язык с пакетами, типами, коллекциями, исключениями и компиляцией в нативный код. Он на порядок мощнее того, что обычно ожидают от «хранимок», и в корпоративных системах в нём часто живёт вся бизнес-логика.

CREATE OR REPLACE PACKAGE BODY billing_pkg AS

  -- Массовая обработка: FORALL вместо построчного цикла
  -- уменьшает переключения контекста PL/SQL <-> SQL на порядки
  PROCEDURE charge_batch(p_period DATE) IS
    TYPE t_ids IS TABLE OF accounts.account_id%TYPE;
    v_ids t_ids;
  BEGIN
    SELECT account_id BULK COLLECT INTO v_ids
      FROM accounts
     WHERE status = 'ACTIVE'
       AND last_charged < p_period;

    FORALL i IN 1 .. v_ids.COUNT SAVE EXCEPTIONS
      UPDATE accounts
         SET balance = balance - monthly_fee,
             last_charged = p_period
       WHERE account_id = v_ids(i);

    COMMIT;
  EXCEPTION
    WHEN OTHERS THEN
      -- каждая упавшая строка доступна в SQL%BULK_EXCEPTIONS
      FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP
        log_error(v_ids(SQL%BULK_EXCEPTIONS(i).ERROR_INDEX),
                  SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
      END LOOP;
      COMMIT;
  END charge_batch;

END billing_pkg;
/

Обратите внимание на FORALL ... SAVE EXCEPTIONS: это идиома, ради которой PL/SQL и держат. Построчный цикл на миллионе строк работает минуты; FORALL — секунды, потому что переключение между PL/SQL-движком и SQL-движком происходит один раз, а не миллион.

Microsoft SQL Server: как он устроен

SQLOS — операционная система внутри СУБД

SQL Server не полагается на планировщик ОС для своих потоков. Слой SQLOS реализует кооперативную многозадачность: на каждое логическое ядро создаётся scheduler, задачи (task) выполняются worker-ами и сами уступают процессор, когда уходят в ожидание. Отсюда вся культура диагностики SQL Server — wait statistics: система честно рассказывает, чего именно она ждала.

-- Топ ожиданий с момента старта, отфильтрованные от служебного шума
SELECT TOP 15
       wait_type,
       wait_time_ms / 1000.0                       AS wait_s,
       waiting_tasks_count                         AS waits,
       (wait_time_ms - signal_wait_time_ms) / 1000.0 AS resource_s,
       signal_wait_time_ms / 1000.0                AS cpu_queue_s
  FROM sys.dm_os_wait_stats
 WHERE wait_type NOT IN ('CLR_SEMAPHORE','SLEEP_TASK','BROKER_TASK_STOP',
                         'XE_TIMER_EVENT','WAITFOR','HADR_FILESTREAM_IOMGR_IOCOMPLETION')
 ORDER BY wait_time_ms DESC;

Читается так: большой PAGEIOLATCH_SH — не хватает памяти или диск медленный; CXPACKET/CXCONSUMER — параллелизм; LCK_M_X — блокировки; PAGELATCH_UP на tempdb — та самая аллокационная конкуренция; большой signal_wait — процессор перегружен.

Хранение: три движка в одном

В SQL Server сосуществуют три разные модели хранения, и понимание их границ — половина мастерства.

Движок Организация Для чего Ограничения
Rowstore (кластерный B-tree) строки в страницах 8 КБ, порядок по ключу кластерного индекса OLTP, точечные операции сканы дорогие
Columnstore колоночное хранение, rowgroup по ~1 млн строк, сжатие аналитика, агрегаты по десяткам миллионов строк плохо для точечных lookup
In-Memory OLTP (Hekaton) строки в памяти, безблокировочный оптимистический MVCC экстремальный OLTP, очереди, staging таблица целиком в RAM, ограничения по типам

Комбинация «кластерный columnstore + некластерный B-tree сверху» даёт гибридную нагрузку (HTAP) без отдельного аналитического хранилища — на объёмах в единицы терабайт это часто дешевле, чем строить пайплайн в ClickHouse (см. https://courses.digitable.life/post/databases/13-clickhouse-and-olap/).

-- Кластерный columnstore для таблицы фактов
CREATE TABLE dbo.fact_sales
(
    sale_id     BIGINT       NOT NULL,
    sale_date   DATE         NOT NULL,
    store_id    INT          NOT NULL,
    product_id  INT          NOT NULL,
    qty         INT          NOT NULL,
    amount      DECIMAL(18,2) NOT NULL
);

CREATE CLUSTERED COLUMNSTORE INDEX ccix_fact_sales ON dbo.fact_sales;

-- Диагностика: сколько строк ушло в дельта-store (не сжато) --
-- если open rowgroups много, значит вставки идут мелкими пачками
SELECT state_description, COUNT(*) AS rowgroups, SUM(total_rows) AS rows_total
  FROM sys.dm_db_column_store_row_group_physical_stats
 WHERE object_id = OBJECT_ID('dbo.fact_sales')
 GROUP BY state_description;

Правило для columnstore: вставляйте пачками не менее 102 400 строк, иначе они попадают в дельта-store в виде B-tree и не сжимаются — производительность падает в разы, а вы будете искать причину в железе.

Изоляция: главная ловушка при переходе с PostgreSQL

Это отличие стоит выделить отдельно, потому что оно ломает приложения при миграции.

По умолчанию SQL Server использует READ COMMITTED на блокировках, а не на версиях строк. Читатель ждёт писателя. В PostgreSQL и Oracle читатель никогда не блокируется.

Лечится включением версионности:

-- Версионный READ COMMITTED: читатели больше не блокируются
ALTER DATABASE billing SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;

-- Отдельный уровень SNAPSHOT (нужен явный SET TRANSACTION ISOLATION LEVEL)
ALTER DATABASE billing SET ALLOW_SNAPSHOT_ISOLATION ON;

Цена: версии строк складываются в tempdb, поэтому tempdb становится критическим ресурсом и её надо выносить на быстрый диск и разбивать на несколько файлов. Но почти для любой OLTP-нагрузки READ_COMMITTED_SNAPSHOT ON — правильный выбор, и удивительно много промышленных систем работает без него, страдая от блокировок «по традиции».

Подробный разбор аномалий — в https://courses.digitable.life/post/databases/07-transactions-and-isolation/.

Always On Availability Groups

Практический вывод из этой диаграммы: синхронная реплика добавляет к каждому коммиту сетевой round-trip. В одном ЦОД это 0.3–1 мс и терпимо; между городами это 10–30 мс, и OLTP-нагрузка с тысячей коммитов в секунду просто встанет. Отсюда стандартная топология: синхронная реплика рядом, асинхронная в DR-площадке.

Издания различаются жёстко: в Standard доступны только Basic Availability Groups — одна база на группу, одна вторичная реплика, читать с неё нельзя. Всё остальное — Enterprise.

T-SQL и планы

-- Идемпотентный upsert без гонки: HOLDLOCK берёт range-блокировку по ключу
MERGE dbo.accounts WITH (HOLDLOCK) AS tgt
USING (VALUES (@account_id, @delta)) AS src(account_id, delta)
   ON tgt.account_id = src.account_id
WHEN MATCHED THEN
    UPDATE SET balance = tgt.balance + src.delta
WHEN NOT MATCHED THEN
    INSERT (account_id, balance) VALUES (src.account_id, src.delta);

Оговорка честности: MERGE в SQL Server исторически имеет длинный список багов и без HOLDLOCK подвержен гонкам. Многие опытные DBA (см. разбор Аарона Бертрана — https://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/) предпочитают явную пару UPDATE + INSERT в транзакции. Знать про это до, а не после инцидента — полезно.

Честное сравнение: Oracle vs SQL Server vs PostgreSQL

Критерий Oracle Database EE SQL Server Enterprise PostgreSQL (для калибровки)
Модель MVCC undo-сегменты, старые версии вне таблицы версии в tempdb (при RCSI) версии в самой таблице + VACUUM
Изоляция по умолчанию READ COMMITTED без блокировки читателей READ COMMITTED на блокировках READ COMMITTED без блокировок
SERIALIZABLE snapshot-семантика (не полный SSI) настоящая, на range-блокировках SSI — предикатные блокировки
Процедурный язык PL/SQL — очень зрелый, пакеты, типы T-SQL — проще, без пакетов PL/pgSQL + любой язык через расширения
Партиционирование мощное, но платная опция включено во все издания с 2016 SP1 декларативное, бесплатно
Колоночное хранение Database In-Memory — платная опция columnstore включён в EE и Std нет в ядре (citus / внешние движки)
Кластер с общим хранилищем RAC — уникален, но платный нет аналога (FCI — только failover) нет
Репликация для чтения Active Data Guard — платно AG readable secondary — только EE hot standby бесплатно
Основная ОС Linux, AIX, Solaris, Windows Windows и Linux (с 2017) всё
Диагностика AWR/ASH — платный Diagnostics Pack Query Store и DMV — бесплатно pg_stat_statements, auto_explain
Порог входа DBA высокий, отдельная профессия средний, много GUI средний
Стоимость входа сотни тысяч USD десятки тысяч USD ноль

Отдельно подчеркну строку про диагностику, потому что на ней ловят чаще всего: запросы к V$ACTIVE_SESSION_HISTORY, отчёты AWR и DBMS_SQLTUNE требуют лицензий Diagnostics Pack и Tuning Pack. Один SELECT из ASH «просто посмотреть» технически работает на EE без лицензии и оставляет след в DBA_FEATURE_USAGE_STATISTICS, который аудитор увидит. В SQL Server и Query Store, и все DMV входят в базовую поставку.

Лицензии: где на самом деле лежат деньги

Как одно железо превращается в разный счёт

Oracle

Две основные метрики:

  • Processor. Количество лицензий = (физические ядра) × (core factor). Core Factor Table — публичный документ Oracle: x86 = 0.5, большинство SPARC/Power = 0.75–1.0. Прайс EE — 47 500 $ за processor, плюс 22% в год за поддержку. Standard Edition 2 — 17 500 $ за сокет, но с жёсткими лимитами: максимум 2 сокета на сервер и не более 16 потоков CPU на инстанс.
  • Named User Plus (NUP). Считаются люди и устройства, минимум 25 NUP на processor для EE. Подходит только для систем с малым и точно известным числом пользователей.

Дальше — опции, и вот здесь бюджет умножается. Каждая покупается на каждый processor, поверх EE:

Опция Прайс за processor Что даёт Аналог в PostgreSQL
Partitioning 11 500 $ секционирование таблиц и индексов встроено бесплатно
Diagnostics Pack 7 500 $ AWR, ASH, метрики pg_stat_statements
Tuning Pack 5 000 $ SQL Tuning Advisor, профили ручной анализ / pg_hint_plan
Advanced Compression 11 500 $ сжатие OLTP-таблиц, бэкапов TOAST + сжатие ФС
Advanced Security 15 000 $ TDE, редактирование данных pgcrypto, шифрование ФС
Real Application Clusters 23 000 $ кластер с общим хранилищем нет аналога
Active Data Guard 11 500 $ чтение со standby hot standby бесплатно
Database In-Memory 23 000 $ колоночный кэш в памяти нет в ядре
Multitenant 17 500 $ больше 3 PDB schemas / отдельные кластеры

Цены — публичный Technology Price List Oracle (https://www.oracle.com/assets/technology-price-list-070617.pdf); реальные контракты обычно идут со скидкой 40–80%, но поддержка считается от прайсовой, а не от скидочной цены, и индексируется ежегодно. Это ключевой механизм: скидка одноразовая, поддержка навсегда.

Три ловушки лицензирования Oracle, которые стоят компаниям миллионы

  1. Виртуализация. Oracle признаёт «hard partitioning» (Oracle VM с закреплением, LPAR, Solaris Zones) и не признаёт VMware как способ ограничить лицензируемые ядра. Формальная позиция — при использовании vSphere и vMotion лицензировать нужно все хосты кластера, куда виртуалка теоретически может мигрировать. Юридически это спорно и в договоре обычно не прописано, но переговорную позицию Oracle это даёт мощную. Практическое следствие: если ставите Oracle на VMware, изолируйте отдельный кластер с закреплённым набором хостов и отключённым vMotion за его пределы.
  2. Случайное включение опций. Опции EE включены в дистрибутиве по умолчанию. Разработчик пишет CREATE TABLE ... PARTITION BY RANGE, и всё — Partitioning использован. В DBA_FEATURE_USAGE_STATISTICS появляется запись, аудитор при проверке предъявляет счёт за все процессоры. Защита — параметр ENABLE_PLUGGABLE_DATABASE, контроль через SELECT name, detected_usages FROM dba_feature_usage_statistics WHERE detected_usages > 0 и регулярное ревью в CI.
  3. Тестовые и DR-среды. Правило Oracle «10-day rule» позволяет держать нелицензированный passive failover-узел не более 10 дней в году суммарно. Standby, который читает — это уже Active Data Guard. Dev-среды формально требуют лицензий (бесплатна только Oracle XE с лимитами 2 ядра / 2 ГБ RAM / 12 ГБ данных).

Microsoft SQL Server

Модель проще и предсказуемее.

Издание Метрика и прайс Лимиты Кому
Express бесплатно 1 ядро активно, 1 ГБ буферный пул, 10 ГБ на БД встраивание, мелкие сервисы
Developer бесплатно функционал Enterprise, запрещён prod dev/test
Web только через хостеров ограниченный shared-хостинг
Standard 3 945 $ за 2-ядерный пакет, либо Server 989 $ + CAL 230 $ 24 ядра, 128 ГБ буферного пула, Basic AG 80% корпоративных задач
Enterprise 15 123 $ за 2-ядерный пакет без лимитов HA, columnstore-масштаб, партиционирование онлайн

Правила счёта: лицензии продаются пакетами по 2 ядра, минимум 4 ядра на физический сокет, минимум 4 ядра на виртуальную машину. Software Assurance (~25–29% в год) даёт право на новые версии, лицензионную мобильность и — важно — Azure Hybrid Benefit, позволяющий переносить лицензии в облако.

Отдельно: с 2019 в Standard дали право лицензировать по виртуальным ядрам ВМ, что для небольших виртуалок радикально дешевле. Пример: 4-vCPU виртуалка со Standard — это 2 пакета, 7 $ 890, против 241 968 $ за Enterprise на голом железе с 32 ядрами. Разница в 30 раз при одинаковом ядре СУБД.

Как выбирать издание — дерево решений

Планы выполнения и замеры

Умение читать план — главный практический навык. Синтаксис в двух системах разный, суть одна: смотрите на расхождение оценки и факта.

Oracle

-- Правильный способ: выполнить и посмотреть ФАКТИЧЕСКИЙ план,
-- а не EXPLAIN PLAN (он показывает лишь предположение оптимизатора)
ALTER SESSION SET STATISTICS_LEVEL = ALL;

SELECT /*+ GATHER_PLAN_STATISTICS */
       c.region, SUM(o.amount) AS total
  FROM orders    o
  JOIN customers c ON c.customer_id = o.customer_id
 WHERE o.order_date >= DATE '2026-01-01'
 GROUP BY c.region;

SELECT * FROM TABLE(
  DBMS_XPLAN.DISPLAY_CURSOR(format => 'ALLSTATS LAST +COST +BYTES'));

Что читать в выводе:

  • E-Rows против A-Rows — оценка против факта. Расхождение более чем в 10 раз означает плохую статистику или сложный предикат; это корень 80% плохих планов.
  • A-Time — накопленное время узла.
  • Buffers — логические чтения; это самая честная метрика стоимости, не зависящая от прогретости кэша.
  • OMem/1Mem/Used-Mem — сколько памяти хотела и получила операция; 1Mem с пометкой пролива означает спилл на диск.
-- Обновление статистики: без него оптимизатор слеп
BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(
    ownname          => 'BILLING',
    tabname          => 'ORDERS',
    estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
    method_opt       => 'FOR ALL COLUMNS SIZE AUTO',  -- гистограммы где нужно
    cascade          => TRUE,                          -- и по индексам тоже
    degree           => 8);
END;
/

SQL Server

SET STATISTICS IO, TIME ON;
SET STATISTICS XML ON;   -- либо «Include Actual Execution Plan» в SSMS

SELECT c.region, SUM(o.amount) AS total
  FROM dbo.orders    AS o
  JOIN dbo.customers AS c ON c.customer_id = o.customer_id
 WHERE o.order_date >= '2026-01-01'
 GROUP BY c.region;

Вывод STATISTICS IO даёт logical reads по каждой таблице — прямой аналог Buffers в Oracle. В плане ищите: Estimated Number of Rows против Actual, оператор Key Lookup (намёк на некрывающий индекс), Sort со spill to tempdb, Table Scan там, где ожидался seek.

Query Store — встроенная история планов, бесплатная и обязательная к включению:

ALTER DATABASE billing SET QUERY_STORE = ON
    (OPERATION_MODE = READ_WRITE,
     MAX_STORAGE_SIZE_MB = 2048,
     QUERY_CAPTURE_MODE = AUTO,
     DATA_FLUSH_INTERVAL_SECONDS = 900);

-- Запросы, у которых план сменился и стало хуже
SELECT TOP 20 q.query_id, qt.query_sql_text,
       p.plan_id, rs.avg_duration / 1000.0 AS avg_ms, rs.count_executions
  FROM sys.query_store_query        q
  JOIN sys.query_store_query_text   qt ON qt.query_text_id = q.query_text_id
  JOIN sys.query_store_plan         p  ON p.query_id = q.query_id
  JOIN sys.query_store_runtime_stats rs ON rs.plan_id = p.plan_id
 WHERE q.query_id IN (SELECT query_id FROM sys.query_store_plan
                       GROUP BY query_id HAVING COUNT(DISTINCT plan_id) > 1)
 ORDER BY rs.avg_duration DESC;

-- Прибить плохой план: принудительно закрепить хороший
EXEC sys.sp_query_store_force_plan @query_id = 4711, @plan_id = 91;

Жизненный цикл плана

Эта схема объясняет parameter sniffing — самую частую загадочную деградацию в обеих СУБД. План строится под значение параметра первого вызова. Если первый вызов был по редкому клиенту (10 строк), оптимизатор выберет nested loops; когда придёт запрос по клиенту с миллионом строк, тот же план исполнится катастрофически медленно.

Лечение в SQL Server:

-- Точечно: пересобирать план каждый раз (цена — CPU на компиляцию)
SELECT ... OPTION (RECOMPILE);

-- Оптимизировать под «средний» случай
SELECT ... OPTION (OPTIMIZE FOR (@customer_id UNKNOWN));

-- Глобально: закрепить проверенный план через Query Store
EXEC sys.sp_query_store_force_plan @query_id = ..., @plan_id = ...;

В Oracle аналог — SQL Plan Baselines и адаптивный курсоринг:

-- Зафиксировать текущий хороший план как baseline
DECLARE
  n PLS_INTEGER;
BEGIN
  n := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
         sql_id => 'a1b2c3d4e5f6g', plan_hash_value => 3721847592);
END;
/

Типичные аварии в проде

Oracle

Симптом Причина Что делать
ORA-01555 snapshot too old долгий запрос, undo перезатёрт увеличить UNDO_RETENTION и размер undo tablespace, RETENTION GUARANTEE, разбить длинный запрос
ORA-04031 в shared pool фрагментация library cache от литералов в SQL использовать bind-переменные, CURSOR_SHARING=FORCE как временный костыль
Внезапный рост library cache: mutex X hard parse storm — приложение генерирует уникальные тексты SQL bind-переменные; проверить ORM на конкатенацию
enq: TX - row lock contention долгая транзакция держит строку найти блокирующего через V$SESSION.BLOCKING_SESSION, ограничить время транзакций
gc buffer busy в RAC горячий блок правят все узлы sequence с CACHE и NOORDER, hash-партиционирование, привязка сервиса к узлу
План «поплыл» после сбора статистики новая кардинальность, cardinality feedback SQL Plan Baseline, PENDING статистика с проверкой перед публикацией
Archiver stuck, база встала заполнена Fast Recovery Area мониторить V$RECOVERY_AREA_USAGE, автоматизировать удаление применённых архивов

SQL Server

Симптом Причина Что делать
Массовые блокировки на чтении READ COMMITTED на блокировках включить READ_COMMITTED_SNAPSHOT
PAGELATCH_UP на tempdb конкуренция за PFS/GAM/SGAM-страницы несколько равных файлов tempdb (по числу ядер, до 8), в 2022 это частично автоматизировано
Индекс есть, но идёт скан неявное преобразование типов (NVARCHAR против VARCHAR, число против строки) привести типы параметров к типам колонок — самая частая и самая дешёвая победа
Резкая деградация процедуры без изменений кода parameter sniffing после рестарта или обновления статистики OPTION (RECOMPILE) точечно, Query Store force plan
Все запросы стали параллельными и медленными MAXDOP = 0 и Cost Threshold for Parallelism = 5 (умолчания из 1998 года) MAXDOP = число ядер в NUMA-узле (но не более 8), CTFP = 40–50
База растёт скачками, паузы на запись autogrowth в 1 МБ или процентах, нет Instant File Initialization фиксированный прирост в мегабайтах, включить IFI (право Perform volume maintenance tasks)
Лог транзакций забил диск recovery model FULL без бэкапов лога, или открытая транзакция регулярные BACKUP LOG, мониторинг log_reuse_wait_desc
Внезапный lock escalation до таблицы обновление более ~5000 строк одной командой батчить по 2000–4000 строк, при необходимости ALTER TABLE ... SET (LOCK_ESCALATION = DISABLE)

Каждая строка этих таблиц — это реальный ночной инцидент, который где-то уже случился. Пройдитесь по ним как по чек-листу до, а не после.

Практика: диагностический минимум

-- Oracle: что происходит прямо сейчас
SELECT s.sid, s.serial#, s.username, s.status, s.event,
       s.blocking_session, s.sql_id,
       ROUND(s.last_call_et/60, 1) AS minutes_in_call
  FROM v$session s
 WHERE s.type = 'USER'
   AND s.status = 'ACTIVE'
 ORDER BY s.last_call_et DESC;

-- Oracle: топ SQL по логическим чтениям на выполнение
SELECT sql_id, executions,
       ROUND(buffer_gets / GREATEST(executions,1))    AS gets_per_exec,
       ROUND(elapsed_time / GREATEST(executions,1)/1000) AS ms_per_exec,
       SUBSTR(sql_text, 1, 90) AS sql_head
  FROM v$sqlarea
 WHERE executions > 50
 ORDER BY buffer_gets DESC
 FETCH FIRST 20 ROWS ONLY;
-- SQL Server: что происходит прямо сейчас (включая блокировщиков)
SELECT r.session_id, r.status, r.wait_type, r.wait_time,
       r.blocking_session_id, r.cpu_time, r.total_elapsed_time,
       SUBSTRING(t.text, (r.statement_start_offset/2)+1,
           ((CASE r.statement_end_offset WHEN -1
             THEN DATALENGTH(t.text) ELSE r.statement_end_offset END
             - r.statement_start_offset)/2)+1) AS running_statement
  FROM sys.dm_exec_requests AS r
 CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
 WHERE r.session_id > 50
 ORDER BY r.total_elapsed_time DESC;

-- SQL Server: топ по средним логическим чтениям
SELECT TOP 20
       qs.execution_count,
       qs.total_logical_reads / qs.execution_count AS avg_reads,
       qs.total_elapsed_time / qs.execution_count / 1000 AS avg_ms,
       SUBSTRING(st.text, 1, 90) AS sql_head
  FROM sys.dm_exec_query_stats AS qs
 CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
 ORDER BY avg_reads DESC;

Обе системы дают одну и ту же картину под разными именами. Если вы умеете читать v$session — вы за час научитесь читать sys.dm_exec_requests, и наоборот.

Различия синтаксиса, о которые спотыкаются при миграции

Задача Oracle SQL Server
Автоинкремент GENERATED ALWAYS AS IDENTITY (12c+) или sequence + триггер IDENTITY(1,1) или SEQUENCE
Ограничение строк FETCH FIRST n ROWS ONLY (12c+), раньше — ROWNUM TOP n или OFFSET ... FETCH
Текущее время SYSDATE, SYSTIMESTAMP GETDATE(), SYSDATETIME()
Конкатенация `
NULL-подстановка NVL, COALESCE ISNULL, COALESCE
Иерархия CONNECT BY PRIOR (или рекурсивный CTE) только рекурсивный CTE
Пустая строка равна NULL — источник тонких багов отличается от NULL
Регистр идентификаторов верхний по умолчанию зависит от collation БД
Разделитель батчей / в SQL*Plus GO в SSMS/sqlcmd
Временные таблицы глобальные, структура постоянна #tmp в tempdb, создаются на лету

Строка про пустую строку — не мелочь. В Oracle '' IS NULL истинно, и приложение, полагающееся на различие пустой строки и NULL, при миграции начинает вести себя иначе в бизнес-логике, а не падать с ошибкой. Это худший вид дефекта.

Когда брать и когда точно не брать

Берите Oracle Database, если:

  • у вас работает сертифицированное под Oracle промышленное ПО (SAP ERP, банковское ядро, Siebel) и вендор не поддержит другое;
  • вам действительно нужен RAC: требование «отказ узла без потери сессий и без переключения адресов» на shared storage;
  • у вас уже есть команда Oracle DBA и амортизированные бессрочные лицензии — тогда миграция может стоить дороже, чем поддержка.

Не берите Oracle, если: вы стартап или продуктовая команда без юриста по лицензиям; ваша нагрузка — обычный OLTP до нескольких терабайт; вы планируете разворачивать много инстансов (dev/test/preprod) — цена умножается на их число; ваш аргумент — «он быстрее».

Берите SQL Server, если:

  • вы уже в экосистеме Microsoft (AD, .NET, Power BI, Azure) — интеграция экономит месяцы;
  • нужна гибридная нагрузка OLTP + аналитика на одном сервере: columnstore в Standard — сильный и недооценённый аргумент;
  • команда состоит из .NET-разработчиков: T-SQL и SSMS дают самый низкий порог входа среди корпоративных СУБД.

Не берите SQL Server, если: вам нужен исходный код и полный контроль; вы работаете преимущественно на Linux/Kubernetes и не хотите платить за то, что даёт PostgreSQL; нагрузка требует Enterprise, а бюджета на него нет — Standard-лимиты (128 ГБ буферного пула) вы упрёте быстрее, чем думаете.

Считайте PostgreSQL по умолчанию (см. https://courses.digitable.life/post/databases/02-postgresql/) — и требуйте обоснования от того, кто предлагает платную СУБД. Обоснование бывает валидным, но оно должно быть письменным и включать TCO на пять лет со всеми средами.

Стратегия ухода: если решили мигрировать

Полный разбор — в https://courses.digitable.life/post/databases/19-choosing-and-migrating/, здесь только специфика корпоративных СУБД.

  1. Инвентаризация того, что нельзя перенести дословно. PL/SQL-пакеты с автономными транзакциями, DBMS_*-вызовы, CLR-сборки в SQL Server, SSIS-пакеты, Oracle Forms. Это, а не таблицы, определяет срок проекта.
  2. Конвертация схемы. Инструменты: ora2pg (https://ora2pg.darold.net/), AWS SCT, pgloader для MSSQL. Автоматика покрывает 70–90% DDL; остаток — руками.
  3. Двойная запись или CDC. Oracle GoldenGate, Debezium с logminer/XStream, для SQL Server — CDC с Debezium. Живая репликация в новую БД позволяет переключаться с минимальным окном. См. https://courses.digitable.life/post/data-engineering/00-overview/.
  4. Теневые прогоны. Отправляйте копию боевого трафика в новую БД, сравнивайте результаты и планы. Регрессия производительности — главная причина отката миграций.
  5. Переключение и — обязательно — снятие лицензий. Формальное прекращение поддержки в договоре, иначе счета продолжают приходить за систему, которая уже выключена.

Не забывайте, что Oracle поддержку нельзя «уменьшить наполовину»: правило matching service levels запрещает снять поддержку с части лицензий из одного заказа, оставив другую часть. Уменьшение обычно означает пересчёт всего контракта. Это отдельная переговорная работа, которую надо начинать за год.

Мини-итог

  • Oracle — вертикальный монолит: всё в одном движке, но продаётся по частям, и каждая часть умножается на число процессоров. Уникальное — RAC, зрелость PL/SQL, Flashback, undo-based MVCC без VACUUM.
  • SQL Server — платформа: понятные издания, всё включено внутри издания, отличная бесплатная диагностика (Query Store, DMV, wait stats), сильный columnstore. Главная ловушка — блокирующий READ COMMITTED по умолчанию.
  • Различие в цене между «правильно выбранным изданием» и «выбранным по привычке» достигает 30–40 раз на одном и том же железе. Лицензионная модель — инженерное решение, а не бухгалтерская формальность.
  • Диагностика в обеих системах строится на одном и том же: логические чтения, расхождение оценки и факта в плане, ожидания. Навык переносится между СУБД почти полностью.
  • Прежде чем покупать — посчитайте TCO на пять лет со всеми dev/test/DR-средами и сравните с PostgreSQL плюс коммерческая подписка на поддержку.

Источники

Что дальше

Мы разобрали самый тяжёлый и дорогой конец спектра СУБД. Теперь пойдём в прямо противоположный: базы, которые живут внутри вашего процесса, не требуют ни администратора, ни лицензии, ни сети — и при этом обходят «взрослые» серверы там, где никто не ожидает.

SQLite и встраиваемые БД: где они сильнее «взрослых» серверов

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов