«Но ведь запрос был быстрым на dev и staging!»
Какие признаки говорят о том, что запрос работает быстро локально, но в production потребует отдельного внимания к производительности?
Есть две точки, после которых простой запрос начинает заметно замедляться:
Сортировка перестаёт помещаться в память.
Сканирование таблицы начинает читать данные с диска.
Примеры ниже показаны на небольших стендах с ограниченными ресурсами. В крупных системах картина будет такой же, только с гораздо большим количеством строк.
Простой запрос:
Без индекса PostgreSQL приходится сканировать всю таблицу, чтобы найти события пользователя, даже если нужных строк совсем немного.
(Небольшая оговорка: если для пользователя нет ни одной строки, PostgreSQL может решить вообще не сканировать таблицу, используя статистику по таблице и колонкам.)
Фаза 1. Всё помещается в память
Таблица содержит 10 000 строк.
Для
Что видно ((фото1):
Все 5 000 строк были отсортированы прямо в RAM.
PostgreSQL всё равно прочитал остальные 5 000 строк и отбросил их.
Все страницы уже находились в памяти (
Фаза 2. Сортировка начинает писать на диск
Теперь в таблице 200 000 строк.
У пользователя уже 100 000 событий.
Что изменилось (2):
Сортировка больше не помещается в память и начинает использовать временные файлы.
PostgreSQL записал 843 временные страницы на диск и затем прочитал их обратно во время merge-фазы.
Ещё 100 тысяч строк были прочитаны только для того, чтобы потом их выбросить.
Фаза 3. Таблица перестаёт помещаться в буферный кеш
Всего уже 600 000 строк.
Из них 100 000 принадлежат нужному пользователю, остальные 500 000 — другим пользователям.
PostgreSQL всё равно вынужден читать их.
Размер таблицы становится больше
(3 фото)
Планировщик запустил 2 параллельных воркера.
Сканирование и сортировка были распределены между 3 процессами, поэтому каждый сортировал примерно по 33 000 строк вместо 100 000.
Но основные проблемы остались:
Таблица перестала помещаться в буферный кеш. Более 3 000 страниц пришлось читать с диска.
Было прочитано и выброшено около 500 тысяч строк.
Все три сортировки всё равно использовали
Параллелизм уменьшил объём работы на каждый процесс, но не убрал запись на диск.
Решение зависит от конкретного приложения.
Самый очевидный вариант:
Такой индекс позволяет резко сократить объём сортировки и избежать полного сканирования таблицы.
Но индекс далеко не единственный вариант.
В зависимости от нагрузки могут помочь:
- кэширование на уровне приложения;
- materialized views;
- партиционирование таблиц;
- изменение модели хранения данных.
Главный вывод простой:
Если в
большие значения
то запрос, который отлично работал на dev-базе с 10 тысячами строк, уже начинает показывать, что будет происходить в production на миллионах записей.
👉 @SQLPortal
Какие признаки говорят о том, что запрос работает быстро локально, но в production потребует отдельного внимания к производительности?
Есть две точки, после которых простой запрос начинает заметно замедляться:
Сортировка перестаёт помещаться в память.
Сканирование таблицы начинает читать данные с диска.
Примеры ниже показаны на небольших стендах с ограниченными ресурсами. В крупных системах картина будет такой же, только с гораздо большим количеством строк.
Простой запрос:
SELECT user_id, event_type, created_at
FROM events
WHERE user_id = 1
ORDER BY created_at DESC
Без индекса PostgreSQL приходится сканировать всю таблицу, чтобы найти события пользователя, даже если нужных строк совсем немного.
(Небольшая оговорка: если для пользователя нет ни одной строки, PostgreSQL может решить вообще не сканировать таблицу, используя статистику по таблице и колонкам.)
Фаза 1. Всё помещается в память
Таблица содержит 10 000 строк.
Для
user_id = 1 найдено 5 000 событий.work_mem = 1MB.Что видно ((фото1):
Sort Method: quicksort Memory: 427kBВсе 5 000 строк были отсортированы прямо в RAM.
Rows Removed by Filter: 5000PostgreSQL всё равно прочитал остальные 5 000 строк и отбросил их.
Buffers: shared hit=74Все страницы уже находились в памяти (
shared_buffers).Фаза 2. Сортировка начинает писать на диск
Теперь в таблице 200 000 строк.
У пользователя уже 100 000 событий.
work_mem всё ещё равен 1 MB.Что изменилось (2):
Sort Method: external merge Disk: 3352kBСортировка больше не помещается в память и начинает использовать временные файлы.
temp read=836 written=843PostgreSQL записал 843 временные страницы на диск и затем прочитал их обратно во время merge-фазы.
Rows Removed by Filter: 100000Ещё 100 тысяч строк были прочитаны только для того, чтобы потом их выбросить.
Фаза 3. Таблица перестаёт помещаться в буферный кеш
Всего уже 600 000 строк.
Из них 100 000 принадлежат нужному пользователю, остальные 500 000 — другим пользователям.
PostgreSQL всё равно вынужден читать их.
Размер таблицы становится больше
shared_buffers.(3 фото)
Планировщик запустил 2 параллельных воркера.
Сканирование и сортировка были распределены между 3 процессами, поэтому каждый сортировал примерно по 33 000 строк вместо 100 000.
Но основные проблемы остались:
shared read=3049Таблица перестала помещаться в буферный кеш. Более 3 000 страниц пришлось читать с диска.
Rows Removed by Filter: 166667 × 3Было прочитано и выброшено около 500 тысяч строк.
Все три сортировки всё равно использовали
external mergeПараллелизм уменьшил объём работы на каждый процесс, но не убрал запись на диск.
Решение зависит от конкретного приложения.
Самый очевидный вариант:
CREATE INDEX idx_events_user_created_at
ON events (user_id, created_at DESC);
Такой индекс позволяет резко сократить объём сортировки и избежать полного сканирования таблицы.
Но индекс далеко не единственный вариант.
В зависимости от нагрузки могут помочь:
- кэширование на уровне приложения;
- materialized views;
- партиционирование таблиц;
- изменение модели хранения данных.
Главный вывод простой:
Если в
EXPLAIN ANALYZE появляются:external mergetemp read / temp writtenбольшие значения
Rows Removed by Filtershared readто запрос, который отлично работал на dev-базе с 10 тысячами строк, уже начинает показывать, что будет происходить в production на миллионах записей.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
VIEW (представления)
🧠 Что такое VIEW?
VIEW — это виртуальная таблица, построенная на основе SQL-запроса.
Данные в ней не хранятся
Хранится только запрос
Проще говоря:
Зачем нужны VIEW?
- упрощают сложные запросы
- позволяют переиспользовать логику
- помогают скрывать чувствительные данные
- делают SQL-код понятнее
Создание VIEW
Использование VIEW
Работает почти так же, как обычная таблица.
Обновление VIEW
Удаление VIEW
Практический пример
Создадим представление со средней зарплатой по отделам:
Использование:
Важные моменты
VIEW не хранит данные
изменения в исходной таблице сразу отражаются в VIEW
VIEW можно использовать в запросах как обычную таблицу
Практика
Создайте VIEW для сотрудников с зарплатой выше 40 000
Создайте VIEW для сотрудников отдела IT
Создайте VIEW со средней зарплатой по отделам
Выполните запросы к созданным VIEW
Удалите одно из представлений
Решения
1. Сотрудники с зарплатой выше 40 000
2. Сотрудники отдела IT
3. Средняя зарплата по отделам
4. Запросы к представлениям
5. Удаление представления
Мини-задача
Создайте VIEW, который показывает 3 сотрудников с самой высокой зарплатой.
Решение
Использование:
Где VIEW используют чаще всего?
- дашборды
- системы отчётности
- аналитические проекты
Потому что представления позволяют скрыть сложную SQL-логику за простым запросом.
👉 @SQLPortal
VIEW — это виртуальная таблица, построенная на основе SQL-запроса.
Данные в ней не хранятся
Хранится только запрос
Проще говоря:
VIEW = сохранённый SQL-запрос, который можно использовать как таблицу
Зачем нужны VIEW?
- упрощают сложные запросы
- позволяют переиспользовать логику
- помогают скрывать чувствительные данные
- делают SQL-код понятнее
Создание VIEW
CREATE VIEW high_salary_emp AS
SELECT name, salary
FROM employees
WHERE salary > 50000;
Использование VIEW
SELECT * FROM high_salary_emp;
Работает почти так же, как обычная таблица.
Обновление VIEW
CREATE OR REPLACE VIEW high_salary_emp AS
SELECT name, salary, department
FROM employees
WHERE salary > 50000;
Удаление VIEW
DROP VIEW high_salary_emp;
Практический пример
Создадим представление со средней зарплатой по отделам:
CREATE VIEW dept_avg_salary AS
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department;
Использование:
SELECT * FROM dept_avg_salary;
Важные моменты
VIEW не хранит данные
изменения в исходной таблице сразу отражаются в VIEW
VIEW можно использовать в запросах как обычную таблицу
Практика
Создайте VIEW для сотрудников с зарплатой выше 40 000
Создайте VIEW для сотрудников отдела IT
Создайте VIEW со средней зарплатой по отделам
Выполните запросы к созданным VIEW
Удалите одно из представлений
Решения
1. Сотрудники с зарплатой выше 40 000
CREATE VIEW high_salary_emp AS
SELECT *
FROM employees
WHERE salary > 40000;
2. Сотрудники отдела IT
CREATE VIEW it_employees AS
SELECT *
FROM employees
WHERE department = 'IT';
3. Средняя зарплата по отделам
CREATE VIEW dept_avg_salary AS
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department;
4. Запросы к представлениям
SELECT * FROM high_salary_emp;
SELECT * FROM it_employees;
SELECT * FROM dept_avg_salary;
5. Удаление представления
DROP VIEW high_salary_emp;
Мини-задача
Создайте VIEW, который показывает 3 сотрудников с самой высокой зарплатой.
Решение
CREATE VIEW top_3_salary AS
SELECT *
FROM employees
ORDER BY salary DESC
LIMIT 3;
Использование:
SELECT * FROM top_3_salary;
Где VIEW используют чаще всего?
- дашборды
- системы отчётности
- аналитические проекты
Потому что представления позволяют скрыть сложную SQL-логику за простым запросом.
Таблица хранит данные.
VIEW хранит запрос.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
PostgreSQL защищает «горячие» данные от вытеснения во время больших последовательных сканирований с помощью Sequential Scan Ring Buffer.
В этом посте посмотрим, сколько раз можно употребить термин
Без Sequential Scan Ring Buffer один запрос вида
Что такое ring buffer?
Когда PostgreSQL обнаруживает большое последовательное сканирование, он переключается на стратегию ring buffer: временное циклическое окно, выделенное внутри
По мере выполнения сканирования страницы проходят через этот буфер по кругу и сразу становятся кандидатами на вытеснение после использования. Благодаря этому основной кеш остаётся изолированным.
Размер ring buffer зависит от типа операции:
Большие последовательные сканирования — базовый размер составляет 32 страницы (256 КБ), но в PostgreSQL 17+ может немного увеличиваться для асинхронного ввода-вывода.
VACUUM — по умолчанию 256 страниц (2 МБ), начиная с PostgreSQL 16. Настраивается через
COPY и другие операции массовой записи — 2048 страниц (16 МБ).
Срабатывает при 25% от
Порог рассчитывается как:
Если размер сканируемой таблицы превышает четверть
Для операций обслуживания действуют отдельные правила. Размер ring buffer для VACUUM задаётся параметром
Что это означает на практике. Данные приложения защищены от вытеснения большими сканированиями. Если рабочий набор помещается в
Результаты последовательного сканирования таблиц, размер которых превышает
Каждый параллельный воркер, выполняющий последовательное сканирование, использует собственный ring buffer. Это увеличивает пропускную способность больших сканирований и одновременно защищает основной пул буферов.
Таблицы, размер которых находится чуть ниже порога в 25% от
👉 @SQLPortal
В этом посте посмотрим, сколько раз можно употребить термин
shared_buffers... поехали.Без Sequential Scan Ring Buffer один запрос вида
SELECT * FROM large_table загрузил бы все страницы большой таблицы в shared_buffers, вытеснив всё, что уже находилось в кеше. Один «холодный» аналитический запрос мог бы полностью разрушить рабочий набор данных всех остальных сессий.Что такое ring buffer?
Когда PostgreSQL обнаруживает большое последовательное сканирование, он переключается на стратегию ring buffer: временное циклическое окно, выделенное внутри
shared_buffers.По мере выполнения сканирования страницы проходят через этот буфер по кругу и сразу становятся кандидатами на вытеснение после использования. Благодаря этому основной кеш остаётся изолированным.
Размер ring buffer зависит от типа операции:
Большие последовательные сканирования — базовый размер составляет 32 страницы (256 КБ), но в PostgreSQL 17+ может немного увеличиваться для асинхронного ввода-вывода.
VACUUM — по умолчанию 256 страниц (2 МБ), начиная с PostgreSQL 16. Настраивается через
vacuum_buffer_usage_limit.COPY и другие операции массовой записи — 2048 страниц (16 МБ).
Срабатывает при 25% от
shared_buffersПорог рассчитывается как:
shared_buffers / 4Если размер сканируемой таблицы превышает четверть
shared_buffers, PostgreSQL использует стратегию ring buffer.Для операций обслуживания действуют отдельные правила. Размер ring buffer для VACUUM задаётся параметром
vacuum_buffer_usage_limit, но PostgreSQL автоматически ограничивает этот буфер значением не более 1/8 от размера shared_buffers.Что это означает на практике. Данные приложения защищены от вытеснения большими сканированиями. Если рабочий набор помещается в
shared_buffers, он останется в кеше даже при запуске крупного последовательного сканирования.Результаты последовательного сканирования таблиц, размер которых превышает
shared_buffers, не будут сохраняться в кеше PostgreSQL. При этом повторные чтения всё ещё могут обслуживаться из page cache операционной системы без обращения к физическому диску.Каждый параллельный воркер, выполняющий последовательное сканирование, использует собственный ring buffer. Это увеличивает пропускную способность больших сканирований и одновременно защищает основной пул буферов.
Таблицы, размер которых находится чуть ниже порога в 25% от
shared_buffers, всё ещё могут вызывать вытеснение данных из кеша.Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
This media is not supported in your browser
VIEW IN TELEGRAM
Вышел Extend UI — open-source набор компонентов для документных агентов.
Что внутри:
✓ 14 готовых компонентов и примеров
✓ Просмотр PDF, DOCX и XLSX
✓ Bounding box citations
✓ Загрузка файлов
✓ Электронная подпись
✓ Полная кастомизация
✓ MIT-лицензия
Команда перепробовала десятки просмотрщиков документов и UI-библиотек, но ни одна не закрывала все их требования по функциональности и UX.
В итоге они собрали собственное решение для Extend.
Изначально проект был внутренним инструментом, но после многочисленных запросов клиентов его решили открыть для сообщества.
Подойдёт для агентных систем, пользовательских документных сценариев и внутренних корпоративных инструментов.
Бонус: компонентами уже ежедневно пользуются на миллионах страниц документов внутри Extend, так что проект успел пройти хорошую проверку в продакшене.
👉 @SQLPortal
Что внутри:
✓ 14 готовых компонентов и примеров
✓ Просмотр PDF, DOCX и XLSX
✓ Bounding box citations
✓ Загрузка файлов
✓ Электронная подпись
✓ Полная кастомизация
✓ MIT-лицензия
Команда перепробовала десятки просмотрщиков документов и UI-библиотек, но ни одна не закрывала все их требования по функциональности и UX.
В итоге они собрали собственное решение для Extend.
Изначально проект был внутренним инструментом, но после многочисленных запросов клиентов его решили открыть для сообщества.
Подойдёт для агентных систем, пользовательских документных сценариев и внутренних корпоративных инструментов.
Бонус: компонентами уже ежедневно пользуются на миллионах страниц документов внутри Extend, так что проект успел пройти хорошую проверку в продакшене.
Please open Telegram to view this post
VIEW IN TELEGRAM
В PostgreSQL 19 Beta 1 завезли
Теперь можно попытаться вставить запись, а если она уже есть — сразу получить существующую.
Похоже, атомарный get-or-create наконец добрался до PostgreSQL.
#PostgreSQL #SQL
👉 @SQLPortal
ON CONFLICT DO SELECT.Теперь можно попытаться вставить запись, а если она уже есть — сразу получить существующую.
Похоже, атомарный get-or-create наконец добрался до PostgreSQL.
#PostgreSQL #SQL
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7
Нумеровать строки в SQL можно по-разному
Пример:
Джесс Рамос показывает разницу между этими функциями на практике и разбирает типичные сценарии их использования.
👉 @SQLPortal
ROW_NUMBER() — уникальный порядковый номер для каждой строкиDENSE_RANK() — одинаковый ранг для одинаковых значений, без пропусков в нумерацииRANK() — одинаковый ранг для одинаковых значений, после совпадений появляются пропускиПример:
score
-----
100
100
90
80
ROW_NUMBER()1
2
3
4
DENSE_RANK()1
1
2
3
RANK()1
1
3
4
Джесс Рамос показывает разницу между этими функциями на практике и разбирает типичные сценарии их использования.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Oracle AI Database 23.26.1 получила поддержку partition by expression.
Теперь секционировать таблицы можно прямо по выражению, а не только по отдельному столбцу.
Например, можно автоматически раскладывать записи по доменам верхнего уровня из email:
То есть
Небольшая фича, которая убирает лишний слой костылей в схемах БД.
Демонстрация от Dani Schnider -https://danischnider.wordpress.com/2026/05/22/partition-by-expression/
👉 @SQLPortal
Теперь секционировать таблицы можно прямо по выражению, а не только по отдельному столбцу.
Например, можно автоматически раскладывать записи по доменам верхнего уровня из email:
PARTITION BY LIST (
REGEXP_SUBSTR(email_address, '[^.]+$')
)
То есть
.com, .org, .net и другие TLD будут попадать в свои партиции без создания отдельного вычисляемого столбца.Небольшая фича, которая убирает лишний слой костылей в схемах БД.
Демонстрация от Dani Schnider -https://danischnider.wordpress.com/2026/05/22/partition-by-expression/
Please open Telegram to view this post
VIEW IN TELEGRAM
Data Warehousing with Oracle
Partition by Expression
In the latest version of the Oracle database, it is now possible to define the partition key of a partitioned table using an expression. This was already possible previously, but only with a small …
👍1🤔1
This media is not supported in your browser
VIEW IN TELEGRAM
Сегодня ковырялся в Stored Procedures в SQL.
По сути, Stored Procedure — это сохранённый набор SQL-инструкций внутри базы данных. Написал один раз, потом вызываешь сколько угодно раз.
Сегодня разобрал три вещи:
• IF / ELSE
• Обработку ошибок
• Нормальный стиль написания процедур
1. IF / ELSE
Stored Procedures умеют принимать решения.
Логика такая же, как в обычном коде:
Например, проверить, сдал студент экзамен или нет:
Точно так же можно проверять:
право голоса по возрасту;
наличие товара на складе;
скидки для клиентов;
права администратора;
любые бизнес-правила.
Теперь стало понятно, что
2. Обработка ошибок
Рано или поздно что-то ломается:
деление на ноль;
дубликаты данных;
обновление несуществующих записей;
ошибки во время денежных переводов.
Для таких случаев в SQL Server есть:
Пример:
Полезная штука:
Позволяет получить текст реальной ошибки:
Ещё посмотрел на транзакции.
Идея простая:
Для денежных переводов это критично.
Если одна из операций упала:
База откатит изменения и не останется в промежуточном состоянии.
3. Стиль написания Stored Procedures
SQL быстро превращается в кашу, если писать как попало.
Плохой вариант:
Нормальный вариант:
Что стоит соблюдать:
понятные названия процедур;
понятные названия параметров;
SQL-ключевые слова в верхнем регистре;
отступы;
комментарии только там, где они реально нужны;
аккуратная структура кода.
Ещё узнал про:
Эта команда отключает лишние сообщения вида:
Мелочь, но в рабочих процедурах её почти всегда добавляют.
Маленькая тема, но без неё сложно писать серьёзные процедуры и автоматизировать работу с данными.
👉 @SQLPortal
По сути, Stored Procedure — это сохранённый набор SQL-инструкций внутри базы данных. Написал один раз, потом вызываешь сколько угодно раз.
Сегодня разобрал три вещи:
• IF / ELSE
• Обработку ошибок
• Нормальный стиль написания процедур
1. IF / ELSE
Stored Procedures умеют принимать решения.
Логика такая же, как в обычном коде:
IF условие
выполнить действие
ELSE
выполнить другое действие
Например, проверить, сдал студент экзамен или нет:
IF @Score >= 50
PRINT 'You passed!';
ELSE
PRINT 'You failed.';
Точно так же можно проверять:
право голоса по возрасту;
наличие товара на складе;
скидки для клиентов;
права администратора;
любые бизнес-правила.
Теперь стало понятно, что
IF ELSE — это основной способ управлять логикой внутри процедуры.2. Обработка ошибок
Рано или поздно что-то ломается:
деление на ноль;
дубликаты данных;
обновление несуществующих записей;
ошибки во время денежных переводов.
Для таких случаев в SQL Server есть:
BEGIN TRY
-- основной код
END TRY
BEGIN CATCH
-- обработка ошибки
END CATCH
Пример:
BEGIN TRY
SELECT @Number1 / @Number2;
END TRY
BEGIN CATCH
PRINT 'Division by zero.';
END CATCH
Полезная штука:
ERROR_MESSAGE()
Позволяет получить текст реальной ошибки:
PRINT ERROR_MESSAGE();
Ещё посмотрел на транзакции.
Идея простая:
либо выполняются все операции, либо не выполняется ни одна.
Для денежных переводов это критично.
Если одна из операций упала:
ROLLBACK TRANSACTION;
База откатит изменения и не останется в промежуточном состоянии.
3. Стиль написания Stored Procedures
SQL быстро превращается в кашу, если писать как попало.
Плохой вариант:
create procedure getstudents as begin select * from students end
Нормальный вариант:
CREATE PROCEDURE GetStudents
AS
BEGIN
SELECT *
FROM Students;
END;
Что стоит соблюдать:
понятные названия процедур;
понятные названия параметров;
SQL-ключевые слова в верхнем регистре;
отступы;
комментарии только там, где они реально нужны;
аккуратная структура кода.
Ещё узнал про:
SET NOCOUNT ON;
Эта команда отключает лишние сообщения вида:
(1 row affected)
Мелочь, но в рабочих процедурах её почти всегда добавляют.
Маленькая тема, но без неё сложно писать серьёзные процедуры и автоматизировать работу с данными.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤2
Некоторые люди — как Kafka.
Постоянно что-то стримят, никогда не останавливаются.
Некоторые — как DuckDB.
Их недооценивают, пока они не начинают работать на безумной скорости.
Некоторые — как Airflow.
Тихо координируют всё происходящее за кулисами.
Некоторые — как Apache Iceberg.
Долго раскачиваются, зато рассчитаны на долгую дистанцию.
Дата твоего рождения покажет, кто ты на самом деле.
Ищи себя ниже 👇
Постоянно что-то стримят, никогда не останавливаются.
Некоторые — как DuckDB.
Их недооценивают, пока они не начинают работать на безумной скорости.
Некоторые — как Airflow.
Тихо координируют всё происходящее за кулисами.
Некоторые — как Apache Iceberg.
Долго раскачиваются, зато рассчитаны на долгую дистанцию.
Дата твоего рождения покажет, кто ты на самом деле.
Ищи себя ниже 👇
👍1
Два data engineer-а спроектировали один и тот же warehouse.
Design A
- SQL
- ETL
- Star Schema
- Data Warehouse
Design B
- Spark
- Data Lakehouse
- Medallion Architecture
- Real-Time Analytics
Какой дизайн согласуют?
A. Design A
B. Design B
Подвох: в компании всего 50 сотрудников.
Защити свой ответ.
👉 @SQLPortal
Design A
- SQL
- ETL
- Star Schema
- Data Warehouse
Design B
- Spark
- Data Lakehouse
- Medallion Architecture
- Real-Time Analytics
Какой дизайн согласуют?
A. Design A
B. Design B
Подвох: в компании всего 50 сотрудников.
Защити свой ответ.
Please open Telegram to view this post
VIEW IN TELEGRAM
image_2026-06-15_08-47-21.png
1.8 MB
Большинство SQL-разработчиков умеют писать запросы.
Но лишь немногие понимают, что происходит автоматически после изменения данных.
Если хочешь разобраться в SQL-триггерах, изучи следующие темы:
1. BEFORE Triggers — триггеры, выполняющиеся до изменения данных.
2. AFTER Triggers — триггеры, выполняющиеся после изменения данных.
3. INSTEAD OF Triggers — триггеры, которые заменяют выполнение операции.
4. Row-Level Triggers — триггеры, срабатывающие для каждой строки.
5. Statement-Level Triggers — триггеры, срабатывающие один раз на SQL-оператор.
6. Audit Logging — аудит и журналирование изменений.
7. Data Validation — проверка данных.
8. Soft Deletes — логическое удаление записей.
9. Business Rule Enforcement — обеспечение соблюдения бизнес-правил.
10. Trigger Performance — производительность триггеров.
11. Nested Triggers — вложенные триггеры.
12. Best Practices для Production — практики использования триггеров в боевых системах.
Освоив эти темы, ты поймёшь, как корпоративные базы данных автоматизируют бизнес-логику в крупных системах.
👉 @SQLPortal
Но лишь немногие понимают, что происходит автоматически после изменения данных.
Если хочешь разобраться в SQL-триггерах, изучи следующие темы:
1. BEFORE Triggers — триггеры, выполняющиеся до изменения данных.
2. AFTER Triggers — триггеры, выполняющиеся после изменения данных.
3. INSTEAD OF Triggers — триггеры, которые заменяют выполнение операции.
4. Row-Level Triggers — триггеры, срабатывающие для каждой строки.
5. Statement-Level Triggers — триггеры, срабатывающие один раз на SQL-оператор.
6. Audit Logging — аудит и журналирование изменений.
7. Data Validation — проверка данных.
8. Soft Deletes — логическое удаление записей.
9. Business Rule Enforcement — обеспечение соблюдения бизнес-правил.
10. Trigger Performance — производительность триггеров.
11. Nested Triggers — вложенные триггеры.
12. Best Practices для Production — практики использования триггеров в боевых системах.
Освоив эти темы, ты поймёшь, как корпоративные базы данных автоматизируют бизнес-логику в крупных системах.
Please open Telegram to view this post
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
Сегодня на практике разбирался с обработкой ошибок в SQL.
Что сделал:
→ Написал хранимую процедуру для добавления заказов в базу данных Sales
→ Обернул её в блок TRY/CATCH
→ При ошибке сохраняются точные значения ERROR_MESSAGE() и ERROR_NUMBER()
→ Никаких тихих падений и гаданий. База данных сама сообщает, что пошло не так.
Почему это важно:
→ Пайплайны ломаются
→ Данные бывают грязными
→ Ошибки неизбежны
Пайплайн без обработки ошибок — это пайплайн, которому нельзя доверять.
В этом и разница между SQL-кодом, который работает на твоём ноутбуке, и SQL-кодом, который выдерживает продакшен.
#DataEngineering #BuildInPublic #SQL
👉 @SQLPortal
Что сделал:
→ Написал хранимую процедуру для добавления заказов в базу данных Sales
→ Обернул её в блок TRY/CATCH
→ При ошибке сохраняются точные значения ERROR_MESSAGE() и ERROR_NUMBER()
→ Никаких тихих падений и гаданий. База данных сама сообщает, что пошло не так.
Почему это важно:
→ Пайплайны ломаются
→ Данные бывают грязными
→ Ошибки неизбежны
Пайплайн без обработки ошибок — это пайплайн, которому нельзя доверять.
В этом и разница между SQL-кодом, который работает на твоём ноутбуке, и SQL-кодом, который выдерживает продакшен.
#DataEngineering #BuildInPublic #SQL
Please open Telegram to view this post
VIEW IN TELEGRAM
В Oracle AI Database 26ai теперь можно определять функции и модули на JavaScript прямо в базе данных.
Это позволяет публиковать JavaScript-код, который затем можно вызывать напрямую из SQL.
#JavaScript #OracleDatabase #SQL #MLE
👉 @SQLPortal
CREATE FUNCTION ... MLE LANGUAGE JAVASCRIPT ...
CREATE MLE MODULE ... LANGUAGE JAVASCRIPT ...
Это позволяет публиковать JavaScript-код, который затем можно вызывать напрямую из SQL.
#JavaScript #OracleDatabase #SQL #MLE
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Написать медленный запрос в Postgres сегодня гораздо сложнее, чем раньше. Запрос может содержать полное сканирование таблицы, крупную сортировку или дорогую агрегацию, но всё равно выполняться быстро. Современный Postgres просто подключает параллельных воркеров.
Параллелизм скрывает стоимость тяжёлых запросов, но не устраняет её. Индексы и механизмы кэширования уменьшают объём работы, который нужно выполнить.
Параллельное выполнение всё равно делает тот же объём работы — оно лишь распределяет его между несколькими процессорами. Воркеры позволяют задействовать больше CPU, но не сокращают количество операций. Если запрос выполняет полное сканирование таблицы, он всё равно прочитает всю таблицу. Просто сделает это быстрее за счёт дополнительных CPU.
На первый взгляд безобидный запрос:
Предположим, что индексов нет, таблица
Параллельный конвейер Postgres
Postgres разделил таблицу на три части, выполнил агрегацию параллельно и затем объединил результаты.
[см. изображение с выводом EXPLAIN ANALYZE]
результаты объединяются через
Несмотря на высокую скорость выполнения, это не лучший вариант для OLTP-базы данных.
При высокой конкурентной нагрузке пул воркеров становится узким местом. Запрос может работать быстро, пока ресурсов хватает, но заметно замедляться при конкуренции за CPU. Запросы с нестабильным временем выполнения — хорошие кандидаты для оптимизации.
Параллельные воркеры не заменяют здоровую архитектуру базы данных:
Добавляйте индексы, чтобы избежать полного сканирования таблиц и дорогостоящих сортировок.
Используйте summary-таблицы или materialized view, чтобы не выполнять тяжёлые агрегации на больших объёмах данных.
Разбивайте time-series таблицы на партиции, чтобы уменьшить объём данных, который приходится сканировать в типовых запросах.
👉 @SQLPortal
Параллелизм скрывает стоимость тяжёлых запросов, но не устраняет её. Индексы и механизмы кэширования уменьшают объём работы, который нужно выполнить.
Параллельное выполнение всё равно делает тот же объём работы — оно лишь распределяет его между несколькими процессорами. Воркеры позволяют задействовать больше CPU, но не сокращают количество операций. Если запрос выполняет полное сканирование таблицы, он всё равно прочитает всю таблицу. Просто сделает это быстрее за счёт дополнительных CPU.
На первый взгляд безобидный запрос:
SELECT
user_id,
count(*) AS total_events
FROM events
GROUP BY user_id
ORDER BY total_events DESC
LIMIT 10;
Предположим, что индексов нет, таблица
events содержит 1 000 000 строк и 10 000 уникальных user_id. Такой запрос выполняет большой объём работы. В любом случае ему придётся прочитать каждую строку таблицы.Параллельный конвейер Postgres
Postgres разделил таблицу на три части, выполнил агрегацию параллельно и затем объединил результаты.
[см. изображение с выводом EXPLAIN ANALYZE]
loops=3 для Seq ScanWorkers Launched: 2 (лидирующий процесс + 2 воркера = всего 3 процесса)Partial HashAggregate выполняется в каждом воркеререзультаты объединяются через
Finalize HashAggregate в лидирующем процессеНесмотря на высокую скорость выполнения, это не лучший вариант для OLTP-базы данных.
При высокой конкурентной нагрузке пул воркеров становится узким местом. Запрос может работать быстро, пока ресурсов хватает, но заметно замедляться при конкуренции за CPU. Запросы с нестабильным временем выполнения — хорошие кандидаты для оптимизации.
Параллельные воркеры не заменяют здоровую архитектуру базы данных:
Добавляйте индексы, чтобы избежать полного сканирования таблиц и дорогостоящих сортировок.
Используйте summary-таблицы или materialized view, чтобы не выполнять тяжёлые агрегации на больших объёмах данных.
Разбивайте time-series таблицы на партиции, чтобы уменьшить объём данных, который приходится сканировать в типовых запросах.
Please open Telegram to view this post
VIEW IN TELEGRAM
В SQL можно объединять таблицы по одинаковым именам колонок через:
Но здесь есть неприятная ловушка
Такой запрос:
будет работать, если
Проблемы начинаются позже.
Если в будущем кто-то добавит колонку
Причина в том, что
Именно поэтому многие разработчики предпочитают более явный вариант:
Кода чуть больше, зато зависимость от структуры таблиц становится очевидной и предсказуемой.
Лукас Эдер показывает этот кейс на простом примере и напоминает, что некоторые удобные сокращения в SQL могут обернуться проблемами спустя месяцы или годы.
https://blog.jooq.org/why-join-using-can-lead-to-errors-in-sql/
👉 @SQLPortal
t1 JOIN t2 USING (c1)
Но здесь есть неприятная ловушка
Такой запрос:
t1
JOIN t2 USING (c1)
JOIN t3 USING (c2)
будет работать, если
c2 есть только в t2 и t3.Проблемы начинаются позже.
Если в будущем кто-то добавит колонку
c2 в t1, запрос внезапно перестанет работать или начнёт вести себя не так, как ожидалось.Причина в том, что
USING опирается на имена колонок, а не на явные ссылки на таблицы. Изменение схемы может неожиданно повлиять на уже существующие JOIN'ы.Именно поэтому многие разработчики предпочитают более явный вариант:
t1
JOIN t2 ON t1.c1 = t2.c1
JOIN t3 ON t2.c2 = t3.c2
Кода чуть больше, зато зависимость от структуры таблиц становится очевидной и предсказуемой.
Лукас Эдер показывает этот кейс на простом примере и напоминает, что некоторые удобные сокращения в SQL могут обернуться проблемами спустя месяцы или годы.
https://blog.jooq.org/why-join-using-can-lead-to-errors-in-sql/
Please open Telegram to view this post
VIEW IN TELEGRAM
Java, SQL and jOOQ.
Avoiding SQL Ambiguities caused by JOIN USING and NATURAL JOIN
Discover the pitfalls of using SQL's NATURAL JOIN and JOIN USING syntax. Learn best practices for joining tables in production queries.
❤3👍2
This media is not supported in your browser
VIEW IN TELEGRAM
DBeaver уже 15 лет остаётся де-факто стандартом среди SQL-клиентов.
Инструмент мощный, но это ещё и тяжёлый Java-монолит, который может запускаться по 20 секунд.
Кто-то решил переписать его с нуля на Rust и добавить возможности, которых в DBeaver никогда не было.
Проект называется Tabularis. 2.5 тыс. звёзд. Лицензия Apache 2.0. Последняя активность — 17 часов назад.
Самое интересное:
Tabularis создал один разработчик — Debba — как эксперимент по AI-assisted разработке.
Цель была простой: проверить, насколько далеко AI-агенты способны зайти при создании реального продукта.
В итоге получилось 55 релизов, 1192 коммита и SQL-клиент, который уже конкурирует с продуктами компаний, где над подобными инструментами работают десятки инженеров.
Что есть в Tabularis, а в DBeaver нет:
✅ Встроенный MCP-сервер — Claude, Cursor и Windsurf могут читать схему БД и выполнять запросы прямо из чата
✅ SQL Notebooks с графиками внутри ячеек и общими переменными между ними
✅ Визуальный EXPLAIN с AI-анализом плана выполнения
✅ Визуальный конструктор запросов с drag-and-drop JOIN'ами
✅ Автоматическая генерация ER-диаграмм
✅ Поддержка PostgreSQL, MySQL/MariaDB, SQLite и ClickHouse через плагины
✅ Редактор на базе Monaco с интеллектуальным автодополнением
✅ Без телеметрии, аккаунтов и подписок
Чего пока нет в Tabularis, но есть в DBeaver:
❌ SQL Server
❌ Oracle
Если работаешь с ними, DBeaver пока остаётся более очевидным выбором.
Во всех остальных случаях Tabularis запускается примерно за 2 секунды, занимает меньше ресурсов и позволяет AI-агентам работать с базой напрямую.
https://github.com/TabularisDB/tabularis
👉 @SQLPortal
Инструмент мощный, но это ещё и тяжёлый Java-монолит, который может запускаться по 20 секунд.
Кто-то решил переписать его с нуля на Rust и добавить возможности, которых в DBeaver никогда не было.
Проект называется Tabularis. 2.5 тыс. звёзд. Лицензия Apache 2.0. Последняя активность — 17 часов назад.
Самое интересное:
Tabularis создал один разработчик — Debba — как эксперимент по AI-assisted разработке.
Цель была простой: проверить, насколько далеко AI-агенты способны зайти при создании реального продукта.
В итоге получилось 55 релизов, 1192 коммита и SQL-клиент, который уже конкурирует с продуктами компаний, где над подобными инструментами работают десятки инженеров.
Что есть в Tabularis, а в DBeaver нет:
✅ Встроенный MCP-сервер — Claude, Cursor и Windsurf могут читать схему БД и выполнять запросы прямо из чата
✅ SQL Notebooks с графиками внутри ячеек и общими переменными между ними
✅ Визуальный EXPLAIN с AI-анализом плана выполнения
✅ Визуальный конструктор запросов с drag-and-drop JOIN'ами
✅ Автоматическая генерация ER-диаграмм
✅ Поддержка PostgreSQL, MySQL/MariaDB, SQLite и ClickHouse через плагины
✅ Редактор на базе Monaco с интеллектуальным автодополнением
✅ Без телеметрии, аккаунтов и подписок
Чего пока нет в Tabularis, но есть в DBeaver:
❌ SQL Server
❌ Oracle
Если работаешь с ними, DBeaver пока остаётся более очевидным выбором.
Во всех остальных случаях Tabularis запускается примерно за 2 секунды, занимает меньше ресурсов и позволяет AI-агентам работать с базой напрямую.
https://github.com/TabularisDB/tabularis
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Каждая база данных Postgres имеет свой профиль нагрузки: одни в основном читают данные, другие — постоянно их записывают. Понимание этого помогает принимать решения по индексам, настройке
Чтение и запись — не одно и то же
Чтение получает страницу размером 8 КБ из
Если страница уже закэширована, стоимость операции почти нулевая.
Если её приходится читать с диска — это одно физическое чтение.
С записью всё сложнее:
• Изменение сначала записывается в WAL, и только после этого транзакция может быть подтверждена
• При первой записи после checkpoint'а может потребоваться запись всей страницы в WAL (
• Обновляются все связанные индексы
• Может выполняться запись в TOAST-таблицы и TOAST-индексы
• Страница данных должна находиться в памяти, поэтому запись часто включает дополнительное чтение
Из-за этого одна операция записи по стоимости ввода-вывода обычно заметно дороже одной операции чтения.
Настройки для read-heavy нагрузок
•
• Индексы на колонках из
• Read replicas — позволяют распределить нагрузку от
•
Настройки для write-heavy нагрузок
• Быстрые накопители (NVMe SSD, высокий IOPS) — записи нельзя обслуживать только за счёт кэша
• Меньше индексов — каждый индекс приходится обновлять при записи; неиспользуемые индексы лучше удалять
• HOT Updates и
• Настройка WAL —
• Более крупный
👉 @SQLPortal
shared_buffers, WAL, репликам чтения и агрессивности autovacuum.Чтение и запись — не одно и то же
Чтение получает страницу размером 8 КБ из
shared_buffers или кэша ОС.Если страница уже закэширована, стоимость операции почти нулевая.
Если её приходится читать с диска — это одно физическое чтение.
С записью всё сложнее:
• Изменение сначала записывается в WAL, и только после этого транзакция может быть подтверждена
• При первой записи после checkpoint'а может потребоваться запись всей страницы в WAL (
full page write)• Обновляются все связанные индексы
• Может выполняться запись в TOAST-таблицы и TOAST-индексы
• Страница данных должна находиться в памяти, поэтому запись часто включает дополнительное чтение
Из-за этого одна операция записи по стоимости ввода-вывода обычно заметно дороже одной операции чтения.
Настройки для read-heavy нагрузок
•
shared_buffers и effective_cache_size — чем больше горячих данных помещается в памяти, тем меньше обращений к диску• Индексы на колонках из
WHERE, JOIN и ORDER BY — выигрыш от ускорения чтения обычно перекрывает накладные расходы на обновление индексов• Read replicas — позволяют распределить нагрузку от
SELECT-запросов без воздействия на primary-узел•
EXPLAIN ANALYZE — помогает находить медленные запросы и заменять последовательное сканирование (Seq Scan) на индексное (Index Scan) там, где это оправданоНастройки для write-heavy нагрузок
• Быстрые накопители (NVMe SSD, высокий IOPS) — записи нельзя обслуживать только за счёт кэша
• Меньше индексов — каждый индекс приходится обновлять при записи; неиспользуемые индексы лучше удалять
• HOT Updates и
fillfactor — Postgres может обновлять строку без изменения индексов, если индексируемые поля не меняются и на странице есть свободное место• Настройка WAL —
wal_buffers уменьшает частоту сбросов WAL, а checkpoint_timeout и checkpoint_completion_target помогают сглаживать пики нагрузки во время checkpoint'ов• Более крупный
shared_buffers — грязные страницы должны находиться в памяти до их записи на диск, поэтому дополнительная память может улучшить производительность систем с интенсивной записью.Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Общие табличные выражения (Common Table Expressions, CTE), известные по ключевому слову
CTE помогают упростить сложные SQL-запросы и сделать их более читаемыми.
Вместо вложенных подзапросов на несколько уровней можно разбить логику на отдельные именованные блоки и затем использовать их как обычные таблицы в основном запросе.
Преимущества:
→ улучшают читаемость сложных запросов
→ позволяют повторно использовать промежуточные результаты
→ упрощают отладку и поддержку SQL-кода
→ поддерживают рекурсивные запросы через
Baraa Khatib Salkini наглядно показывает, как работают CTE и почему они делают сложные SQL-запросы гораздо понятнее.
👉 @SQLPortal
WITH, позволяют создавать именованные подзапросы:WITH cte AS (
SELECT ...
)
SELECT *
FROM cte;
CTE помогают упростить сложные SQL-запросы и сделать их более читаемыми.
Вместо вложенных подзапросов на несколько уровней можно разбить логику на отдельные именованные блоки и затем использовать их как обычные таблицы в основном запросе.
Преимущества:
→ улучшают читаемость сложных запросов
→ позволяют повторно использовать промежуточные результаты
→ упрощают отладку и поддержку SQL-кода
→ поддерживают рекурсивные запросы через
WITH RECURSIVEBaraa Khatib Salkini наглядно показывает, как работают CTE и почему они делают сложные SQL-запросы гораздо понятнее.
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
SQL CTE (Common Table Expression) Visually Explained | Full Guide WITH Clause | #SQL Course 28
Visually explained how SQL CTEs work using the WITH clause to simplify complex queries and improve readability.
Want More? 👇
- *Free Download Materials* https://www.datawithbaraa.com/wiki/sql#sql-welcome
- *Free SQL Course* https://youtu.be/SSKVgrwhzus…
Want More? 👇
- *Free Download Materials* https://www.datawithbaraa.com/wiki/sql#sql-welcome
- *Free SQL Course* https://youtu.be/SSKVgrwhzus…
❤4
Кстати, если пропустили:
Вышла Oracle AI Database 23.26.2 Free.
Можно скачать и попробовать новые возможности SQL, включая:
вложенные
https://www.oracle.com/database/free/get-started/
👉 @SQLPortal
Вышла Oracle AI Database 23.26.2 Free.
Можно скачать и попробовать новые возможности SQL, включая:
JOIN TO ONEWAIT/NOWAIT для DML-операцийвложенные
WITH-выражения (Nested WITH)https://www.oracle.com/database/free/get-started/
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3