Теоретический конспект лекции по дисциплине «ОП.08 Основы проектирования баз данных» (§2.2 с.7 программы)
В эпоху файловых систем 1960-х годов программы писались с жесткой привязкой к физической структуре файлов на магнитных лентах и барабанах. Каждое приложение лично знало:
Выход из кризиса предложил комитет ANSI/X3/SPARC в 1975 году, разделив архитектуру СУБД на три строго изолированных уровня.
Архитектура ANSI/SPARC гарантирует, что пользователь и прикладное ПО взаимодействуют не с байтами на накопителе, а с абстрактными логическими отношениями.
SELECT client_name, inn FROM Clients WHERE client_id = 42;.Clients — это представление (VIEW). Парсер раскрывает макроподстановку и переписывает запрос к реальным концептуальным таблицам Legal_Entities и Individuals через UNION ALL.client_id, проверяет кэш страниц в оперативной памяти (Shared Buffers) и генерирует команды дискового ввода-вывода (IO) для считывания конкретной 8-килобайтной страницы с накопителя NVMe.Когда бизнес растет, структура сущностей меняется: таблицы разбиваются на части (вертикальная или горизонтальная декомпозиция), объединяются, добавляются новые обязательные атрибуты.
Исходная ситуация: Существовала единая таблица Clients(client_id, name, inn, passport, kpp, address). С ней работает старая монолитная бухгалтерская программа, исходные коды которой утеряны или изменение которых стоит слишком дорого.
Потребность рефакторинга: Для соблюдения 3-й нормальной формы и исключения аномалий решено разделить ее на две специализированные таблицы:
Legal_Entities (Юридические лица: inn, kpp, ogrn, legal_name)Individuals (Физические лица: inn, passport_series, passport_number, full_name)Решение с помощью VIEW (сохранение логической независимости):
-- 1. Новая концептуальная схема:
CREATE TABLE Legal_Entities (
client_id SERIAL PRIMARY KEY,
company_name VARCHAR(255) NOT NULL,
inn VARCHAR(12) NOT NULL,
kpp VARCHAR(9) NOT NULL,
address TEXT NOT NULL
);
CREATE TABLE Individuals (
client_id SERIAL PRIMARY KEY,
full_name VARCHAR(255) NOT NULL,
inn VARCHAR(12),
passport_data VARCHAR(50) NOT NULL,
address TEXT NOT NULL
);
-- 2. Экранирующее представление для старой бухгалтерской программы:
CREATE OR REPLACE VIEW Clients AS
SELECT client_id, company_name AS name, inn, NULL AS passport, kpp, address, 'LEGAL' AS client_type
FROM Legal_Entities
UNION ALL
SELECT client_id, full_name AS name, inn, passport_data AS passport, NULL AS kpp, address, 'INDIVIDUAL' AS client_type
FROM Individuals;
-- 3. Обеспечение операций модификации через триггер INSTEAD OF:
CREATE OR REPLACE FUNCTION process_clients_insert()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.kpp IS NOT NULL THEN
INSERT INTO Legal_Entities (client_id, company_name, inn, kpp, address)
VALUES (NEW.client_id, NEW.name, NEW.inn, NEW.kpp, NEW.address);
ELSE
INSERT INTO Individuals (client_id, full_name, inn, passport_data, address)
VALUES (NEW.client_id, NEW.name, NEW.inn, COALESCE(NEW.passport, 'N/A'), NEW.address);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_clients_insert
INSTEAD OF INSERT ON Clients
FOR EACH ROW EXECUTE FUNCTION process_clients_insert();
Clients. Ни одна строчка клиентского программного кода не была переписана. Логическая независимость данных достигнута!
В интернет-магазине таблица Orders насчитывает 10 000 000 строк. Микросервис чекаута выполняет поиск по идентификатору пользователя:
SELECT order_id, order_date, total_sum
FROM Orders
WHERE user_id = 582103;
Ситуация до оптимизации: Индекс отсутствует. СУБД выполняет Sequential Scan (полное последовательное сканирование диска). Запрос читает 800 МБ страниц и выполняется 3.8 секунды. Сервер перегружен.
Действие администратора БД (DBA):
-- Создание B-Tree индекса на физическом уровне:
CREATE INDEX idx_orders_user_id ON Orders USING btree(user_id);
Ситуация после оптимизации: Запрос приложения не изменился ни на один символ. Однако оптимизатор PostgreSQL автоматически выбирает план Bitmap Index Scan / Index Scan. Время отклика падает с 3800 мс до 1.2 мс.
| Инструмент СУБД | Физическое действие | Поведение прикладного SQL |
|---|---|---|
| Секционирование (Partitioning) | Таблица разбивается на диске на секции по годам (orders_2024, orders_2025). |
Приложение шлет FROM Orders WHERE order_date >= .... СУБД сама считывает только нужный файл (Partition Pruning). |
| Табличные пространства (Tablespaces) | Архивные таблицы переносятся с дорогого SSD NVMe на дешевый медленный HDD-массив RAID-6. | Запросы чтения архива работают идентично; схема таблиц и имена колонок остаются прежними. |
| Сжатие данных (Compression) | Включение алгоритма LZ4 для дисковых блоков таблицы. | Приложение не знает о сжатии; распаковка выполняется ядром СУБД прозрачно в оперативной памяти. |
| Характеристика | Логическая независимость | Физическая независимость |
|---|---|---|
| Граница уровней ANSI/SPARC | Между Внешним и Концептуальным | Между Концептуальным и Внутренним |
| Что подвергается изменению? | Концептуальная схема (разбиение таблиц, добавление атрибутов, нормализация) | Физическая схема (индексы, таблицы размещения, дисковые массивы, секционирование) |
| Что защищается от правок? | Внешние приложения, отчеты, старые сервисы, клиентские формы | Концептуальная схема и SQL-запросы приложений |
| Основные инструменты реализации | Представления (VIEW), материализованные представления, триггеры INSTEAD OF | Индексы (B-Tree/Hash/GiST), секционирование (PARTITION BY), Tablespaces, сжатие |
| Сложность достижения на практике | Высокая (требует глубокого анализа бизнес-логики и эквивалентности данных) | Средняя / Низкая (автоматизирована ядром СУБД и оптимизатором запросов) |
UNION ALL доступно только для чтения. Чтобы сохранить возможность выполнять операции INSERT, UPDATE и DELETE со стороны старых систем, СУБД требует создания триггеров INSTEAD OF, которые берут на себя логику распределения данных по новым таблицам.