Первое, что мы проверили — структуру таблиц. База данных «МедИнфо» была нормализована до 3NF (третья нормальная форма), что является стандартной практикой. Однако в некоторых случаях избыточная нормализация создавала проблемы производительности.
Нормальные формы — это правила декомпозиции таблиц для устранения логической избыточности данных:
- 1NF — атомарность: каждая ячейка содержит одно значение, нет повторяющихся групп
- 2NF — полная функциональная зависимость: каждый неключевой атрибут зависит от всего первичного ключа
- 3NF — отсутствие транзитивных зависимостей: неключевые атрибуты не зависят друг от друга
- BCNF (Бойса-Кодда) — каждая функциональная зависимость определяется суперключом
Запрос карточки пациента требовал соединения 7 таблиц:
-- Исходный запрос: 45 секунд на пациенте с длинной историей
SELECT
p.last_name, p.first_name, p.birth_date,
v.visit_date, v.complaint,
d.diagnosis_code, d.diagnosis_name,
doc.last_name AS doctor_name, doc.specialization,
pr.prescription_text, pr.dosage,
dept.department_name,
ins.insurance_number, ins.company_name
FROM patients p
JOIN visits v ON v.patient_id = p.id
JOIN diagnoses d ON d.visit_id = v.id
JOIN doctors doc ON doc.id = v.doctor_id
JOIN prescriptions pr ON pr.visit_id = v.id
JOIN departments dept ON dept.id = doc.department_id
JOIN insurance ins ON ins.patient_id = p.id
WHERE p.id = 12345
ORDER BY v.visit_date DESC;
Мы применили стратегическую денормализацию: добавили материализованное представление для часто запрашиваемых данных карточки:
-- Материализованное представление для быстрого доступа
CREATE MATERIALIZED VIEW mv_patient_card AS
SELECT
p.id AS patient_id,
p.last_name || ' ' || p.first_name AS patient_name,
p.birth_date,
v.id AS visit_id,
v.visit_date,
v.complaint,
d.diagnosis_code,
d.diagnosis_name,
doc.last_name AS doctor_name,
doc.specialization,
dept.department_name,
ins.insurance_number
FROM patients p
JOIN visits v ON v.patient_id = p.id
LEFT JOIN diagnoses d ON d.visit_id = v.id
JOIN doctors doc ON doc.id = v.doctor_id
JOIN departments dept ON dept.id = doc.department_id
LEFT JOIN insurance ins ON ins.patient_id = p.id;
-- Индекс на материализованном представлении
CREATE UNIQUE INDEX idx_mv_patient_card
ON mv_patient_card (patient_id, visit_id);
-- Автоматическое обновление каждые 5 минут
SELECT cron.schedule('refresh_patient_card',
'*/5 * * * *',
'REFRESH MATERIALIZED VIEW CONCURRENTLY mv_patient_card');
Время запроса карточки сократилось с 45 секунд до 120 мс.
Оставить комментарий