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 делает бесплатно?
Ответ состоит из четырёх частей, и только одна из них техническая.
- Ответственность. Когда банковская АБС падает в 3 часа ночи, кто-то должен взять трубку и иметь контрактное обязательство починить. Open source-подписка (EDB, Percona) это тоже даёт, но у Oracle за спиной сорок лет судебной практики и регуляторного признания.
- Регуляторика и сертификация. Промышленное ПО — SAP, Siebel, банковские ядра, MES-системы на заводах — сертифицировано под конкретные версии конкретных СУБД. Вы не «выбираете БД», вы получаете её в комплекте.
- Интегрированный стек. Oracle продаёт связку Exadata + RAC + Data Guard + GoldenGate, где всё протестировано друг с другом. Microsoft продаёт связку SQL Server + SSIS + SSAS + SSRS + Power BI + Active Directory, где аутентификация и BI «просто работают».
- Технические возможности, которых в 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 на общем хранилище.
Разберём ключевые элементы.
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
через interconnect"| N2 ASM[("Общее хранилище ASM
одна database")] N1 --> ASM N2 --> ASM end subgraph STANDBY["Резервный ЦОД"] S1["Standby instance"] ASM2[("Standby database")] S1 --> ASM2 end ASM -->|"redo-поток
Data Guard"| ASM2 APP["Приложение"] -->|"SCAN-listener
прозрачный failover"| PRIMARY APP -.->|"после switchover"| STANDBY STANDBY -->|"Active Data Guard:
читающие отчёты"| BI["BI и выгрузки"]
- 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, которые стоят компаниям миллионы
- Виртуализация. Oracle признаёт «hard partitioning» (Oracle VM с закреплением, LPAR, Solaris Zones) и не признаёт VMware как способ ограничить лицензируемые ядра. Формальная позиция — при использовании vSphere и vMotion лицензировать нужно все хосты кластера, куда виртуалка теоретически может мигрировать. Юридически это спорно и в договоре обычно не прописано, но переговорную позицию Oracle это даёт мощную. Практическое следствие: если ставите Oracle на VMware, изолируйте отдельный кластер с закреплённым набором хостов и отключённым vMotion за его пределы.
- Случайное включение опций. Опции 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. - Тестовые и 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 раз при одинаковом ядре СУБД.
Как выбирать издание — дерево решений
больше 128 ГБ
буферного пула?"} -->|"Нет"| B{"Нужны читаемые
реплики или
несколько БД в AG?"} A -->|"Да"| E["SQL Server Enterprise
или пересмотр модели данных"] B -->|"Нет"| C{"Нужно онлайн-
перестроение индексов
и партиционирование
под нагрузкой?"} B -->|"Да"| E C -->|"Нет"| D["SQL Server Standard
лицензировать по vCPU
виртуальной машины"] C -->|"Да"| E E --> F{"Считали TCO против
PostgreSQL + подписка
вендора поддержки?"} F -->|"Нет"| G["Посчитайте.
Разница часто 10-20x"] F -->|"Да, обосновано"| H["Покупайте,
но зафиксируйте цену
поддержки в договоре"]
Планы выполнения и замеры
Умение читать план — главный практический навык. Синтаксис в двух системах разный, суть одна: смотрите на расхождение оценки и факта.
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/, здесь только специфика корпоративных СУБД.
- Инвентаризация того, что нельзя перенести дословно. PL/SQL-пакеты с автономными транзакциями,
DBMS_*-вызовы, CLR-сборки в SQL Server, SSIS-пакеты, Oracle Forms. Это, а не таблицы, определяет срок проекта. - Конвертация схемы. Инструменты:
ora2pg(https://ora2pg.darold.net/), AWS SCT,pgloaderдля MSSQL. Автоматика покрывает 70–90% DDL; остаток — руками. - Двойная запись или CDC. Oracle GoldenGate, Debezium с
logminer/XStream, для SQL Server — CDC с Debezium. Живая репликация в новую БД позволяет переключаться с минимальным окном. См. https://courses.digitable.life/post/data-engineering/00-overview/. - Теневые прогоны. Отправляйте копию боевого трафика в новую БД, сравнивайте результаты и планы. Регрессия производительности — главная причина отката миграций.
- Переключение и — обязательно — снятие лицензий. Формальное прекращение поддержки в договоре, иначе счета продолжают приходить за систему, которая уже выключена.
Не забывайте, что 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 плюс коммерческая подписка на поддержку.
Источники
- Oracle Database Concepts, 23ai — https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/
- Oracle Database Performance Tuning Guide — https://docs.oracle.com/en/database/oracle/oracle-database/19/tgdba/
- Oracle Technology Price List — https://www.oracle.com/assets/technology-price-list-070617.pdf
- Oracle Partitioning Policy (core factor, hard/soft partitioning) — https://www.oracle.com/assets/partitioning-070609.pdf
- Tom Kyte, «Expert Oracle Database Architecture» — канонический разбор undo, redo и блокировок
- Jonathan Lewis, «Cost-Based Oracle Fundamentals» — как на самом деле думает оптимизатор Oracle
- SQL Server Documentation — https://learn.microsoft.com/en-us/sql/sql-server/
- SQL Server 2022 Editions and supported features — https://learn.microsoft.com/en-us/sql/sql-server/editions-and-components-of-sql-server-2022
- SQL Server Licensing Datasheet — https://www.microsoft.com/en-us/sql-server/sql-server-2022-pricing
- Kalen Delaney и др., «SQL Server Internals» — устройство SQLOS, storage engine, версионности
- Brent Ozar, диагностические скрипты
sp_Blitz/sp_BlitzFirst— https://www.brentozar.com/first-aid/ - Erik Darling про parameter sniffing и планы — https://erikdarling.com/
- Paul Randal, wait statistics и внутренности хранения — https://www.sqlskills.com/help/waits/
- ora2pg — миграция Oracle в PostgreSQL — https://ora2pg.darold.net/
Что дальше
Мы разобрали самый тяжёлый и дорогой конец спектра СУБД. Теперь пойдём в прямо противоположный: базы, которые живут внутри вашего процесса, не требуют ни администратора, ни лицензии, ни сети — и при этом обходят «взрослые» серверы там, где никто не ожидает.
SQLite и встраиваемые БД: где они сильнее «взрослых» серверов