

- Главная
- Каталог
- Интернет технологии
- Postgres Guru | Базы данных

Postgres Guru | Базы данных
Технические статьи, инструкции, новости и юмор из мира СУБД PostgreSQL и языка запросов SQL.
Статистика канала
Полная статистикаchevron_rightВ предверии выхода 18-й версии PostgreSQL (25.09.2025), посмотрим на ее ключевое нововведение - Асинхронный I/O, которое обещает кардинально улучшить производительность на высоконагруженных системах.
Что такое асинхронный I/O и почему это важно?
Чтобы понять значимость этой функции, давайте разберемся в основе проблемы.
Синхронный I/O (как работало раньше): Когда процессу PostgreSQL (например, backend, выполняющему ваш SQL-запрос) нужно прочитать данные с диска (страницу из таблицы или индекса), он инициирует операцию чтения и блокируется, ожидая ее завершения. Процесс просто "спит", пока аппаратное обеспечение (диск, SSD) не выполнит запрос и не вернет данные. Только получив данные, процесс может продолжить свою работу. Аналогично, хотя и с нюансами, могла работать и запись. Это простое и надежное решение, но на быстрых NVMe SSD процессор может простаивать в ожидании, тратя драгоценное время на простои.
Асинхронный I/O (как работает теперь): Процесс инициирует операцию чтения и немедленно возвращается к выполнению другой полезной работы. Он не блокируется. Когда операция ввода-вывода завершается, ядро ОС уведомляет процесс, и тот обрабатывает результат. Это позволяет максимально загрузить процессор и параллельно обрабатывать множество запросов к диску.
Как реализован асинхронный I/O в PostgreSQL 18?
Важно отметить, что операции записи все равно останутся синхронными, это необходимо для обеспечения принципов ACID транзакций.
1️⃣ Низкоуровневая интеграция: Разработчики сообщества PostgreSQL, в частности Андрес Фройнд, проделали огромную работу по интеграции механизма io_uring (для Linux) и POSIX AIO (для других UNIX-систем) непосредственно в движок базы данных. Раньше PostgreSQL эмулировал асинхронность с помощью пулов процессов, но это было не истинно асинхронно.
2️⃣ io_uring: На системах Linux 5.1+ используется современный и высокопроизводительный интерфейс io_uring, который позволяет с минимальными накладными расходами ставить в очередь десятки тысяч операций ввода-вывода и эффективно обрабатывать их завершение. По-умолчанию настройка будет io_method = worker, но если ваша ОС позволяет использовать io_ring, то лучше использовать его;
3️⃣ Прозрачность для пользователя: вам, администратору, не нужно переписывать запросы или приложения. СУБД автоматически начинает использовать AIO для фоновых процессов, таких как:
✅ CHECKPOINT (очень важная операция, записывающая "грязные" буферы на диск)
✅ Фоновый writer (Background Writer)
✅ Автоочистка (AutoVacuum)
✅ А также для операций чтения/записи пользовательских процессов.
Какие преимущества это дает?
1️⃣ Значительный рост производительности на быстрых дисках (NVMe): На современных SSD, особенно NVMe, где задержки измеряются микросекундами, накладные расходы на синхронные ожидания становятся очень заметными;
2️⃣ Снижение задержек (Latency) и увеличение пропускной способности (Throughput);
3️⃣ Более эффективный CHECKPOINT: Эта критически важная операция теперь будет выполняться гораздо менее болезненно для основной нагрузки, так как запись будет происходить асинхронно, не заставляя процессы надолго блокироваться в ожидании.
4️⃣ Улучшение масштабируемости: Система будет лучше масштабироваться на многоядерных серверах, так как потоки и процессы будут меньше времени тратить на ожидание I/O и больше на полезную работу.
Ограничения и требования
✅ Поддержка ОС: Для работы в полную силу требуется современное ядро Linux (поддерживающее io_uring) или другая ОС с поддержкой POSIX AIO;
✅ Тип нагрузки: Наибольшую выгоду получат workloads, интенсивно работающие с диском. Для баз данных, которые полностью помещаются в кэш shared_buffers (в оперативной памяти), прирост может быть не так заметен;
✅ Это все еще развивающаяся функция: В первом релизе (PostgreSQL 18) асинхронный I/O, скорее всего, будет включен не для всех операций, а в первую очередь для фоновых процессов.
#pgnews
UPDATE fruit
SET quantity = 300
WHERE item = 'Apples'
RETURNING OLD.*, NEW.*;
Вывод:
id|item|quantity|id|item|quantity
——+—-——+————————+——+———+——————
5|Apples| 200 | 5| Apples |300
(1 row)
Получается очень просто, удобно и наглядно! Все как мы любим! ☺️
Вот еще пример.
Допустим, мы выполняем операцию UPSERT (INSERT ... ON CONFLICT ... DO UPDATE) и хотим определить, была ли строка, возвращённая RETURNING, новой вставленной строкой или существующей обновлённой строкой. Это было возможно и раньше, но полагалось на неинтуитивную проверку xmax = 0:
INSERT INTO webhook (
id,
data
) VALUES (
@id,
@data
)
ON CONFLICT (id)
DO UPDATE SET id = webhook.id
RETURNING webhook.*,
(xmax = 0) AS is_new;
Этот оператор полагается на то, что xmax устанавливается в ноль для новой вставки, что является особенностью реализации блокировок в PostgreSQL. Это работает, но не является гарантированной частью API и потенциально может измениться в любой момент.
В PostgreSQL 18 мы можем переписать приведённый выше код так, чтобы он был более понятным и не зависел от деталей реализации. Это также просто - достаточно проверить, является ли OLD значением NULL в предложении RETURNING:
INSERT INTO webhook (
id,
data
) VALUES (
@id,
@data
)
ON CONFLICT (id)
DO UPDATE SET id = webhook.id
RETURNING webhook.*,
(OLD IS NULL)::boolean AS is_new;
Доступ к OLD и NEW, без сомнения, найдёт множество других полезных применений, но этот пример позволяет нам сразу же улучшить код, написанный до 18-й версии.
Переходим на PostgreSQL 18 и пользуемся! ☺️
#pgnews #pgbaseCREATE INDEX idx_sales_region_category_date
ON sales (region, category, date);
-Этот запрос использует индекс эффективно:
SELECT * FROM sales
WHERE region = 'North' AND category = 'Electronics';
-Этот запрос не может использовать индекс эффективно (до PG18):
SELECT * FROM sales
WHERE category = 'Electronics' AND date > '2025-01-01';
Второй запрос выше традиционно требовал от PostgreSQL сканировать все записи индекса или возвращаться к последовательному сканированию таблицы, поскольку он не указывает ведущий столбец region.
Skip scan решает эту проблему, позволяя PostgreSQL эффективно ориентироваться в структуре индекса, даже если ведущие столбцы отсутствуют в запросе. Это достигается за счет идентификации различных значений в неуказанных ведущих столбцах и выполнения целевого сканирования для каждого значения.
Оптимизация skip scan наиболее эффективна когда ведущие столбцы будут с низкой кардинальностью. Т. е. Skip scan работает лучше всего, когда пропущенные ведущие столбцы имеют относительно немного уникальных значений. Оптимизация работает, по сути, выполняя отдельные сканирования индекса для каждого уникального значения в пропущенных ведущих столбцах, поэтому меньше уникальных значений означает меньше отдельных сканирований.
Хотя skip scan значительно улучшает производительность запросов во многих сценариях, важно понимать его характеристики и ограничения.
Skip scan полезен не во всех ситуациях:
❌ Ведущие столбцы с высокой кардинальностью. Когда ведущий столбец имеет слишком много уникальных значений, skip scan становится неэффективным;
❌ Большие результирующие наборы.
Когда ваш запрос возвращает большую часть таблицы, последовательное сканирование обычно быстрее.
Планировщик запросов PostgreSQL автоматически определяет, когда skip scan целесообразен, на основе статистики таблицы и оценок стоимости. Вам не нужно вручную настраивать, когда его использовать - планировщик выбирает наиболее эффективный подход для каждого запроса.
Поддерживайте актуальность статистики таблиц, чтобы обеспечить принятие планировщиком хороших решений об использовании skip scan. Свежая статистика помогает PostgreSQL точно оценивать кардинальность столбцов индекса и принимать оптимальные решения по использованию skip scan.
Ограничения
Skip scan в PostgreSQL 18 имеет определенную область применения:
📌 Работает только с индексами типа B-tree;
📌 Наиболее эффективен, когда пропущенные ведущие столбцы имеют низкую кардинальность;
📌 Требует по крайней мере одно условие равенства для последующего столбца в индексе;
📌 Преимущество в производительности уменьшается по мере роста количества уникальных значений в пропущенных столбцах.
Ждем PostgreSQL 18 с нетерпением! 😁
#pgnewsстрока SIMILAR TO шаблон [ESCAPE 'escape-символ'];
Зачем он нужен? Представьте ситуацию: вам нужно найти номера телефонов в формате (XXX) XXX-XXXX.
С LIKE это возможно, но запрос получится очень громоздким и нечитаемым: вам пришлось бы использовать несколько LIKE с OR и точно считать количество символов.
Полноценное регулярное выражение с оператором ~ справится с этим, но его синтаксис может показаться сложным для простых задач.
SIMILAR TO идеально подходит для таких "промежуточных" случаев, предлагая более выразительный способ описания шаблона, чем LIKE, но не такой сложный, как полноценные регулярные выражения.
Арсенал метасимволов у SIMILAR TO гораздо больше, чем у LIKE:
% - любая последовательность из нуля или более символов;
_ - любой одиночный символ;
\| Выбор (OR) - указывает альтернативные варианты шаблона;
* - повторение предыдущего элемента нуль или более раз;
+ - повторение предыдущего элемента один или более раз;
? - повторение предыдущего элемента нуль или один раз;
{m} - повторение предыдущего элемента ровно m раз;
{m,} - повторение предыдущего элемента m или более раз;
{m,n} - повторение предыдущего элемента не менее m и не более n раз;
[...] - список символов (как в [abc]) или диапазон (как в [a-z]). Совпадает с любым одним символом из списка;
[^...] - отрицательный список символов. Совпадает с любым одним символом, которого нет в списке;
() - группировка элементов.
Важное примечание: Поскольку эти метасимволы являются частью синтаксиса SQL, обратная косая черта \ НЕ является экранирующим символом по умолчанию. Чтобы использовать литеральные версии этих символов в шаблоне (например, найти сам знак %), их нужно экранировать с помощью предложения ESCAPE.
Примеры;
Допустим, у нас есть таблица products с полем name.
1️⃣ Простой поиск (как в LIKE):
- Найти товары, название которых начинается на 'Про':
SELECT name FROM products WHERE name SIMILAR TO 'Про%';
2️⃣ Использование выбора (|):
- Найти товары, либо 'стол', либо 'стул':
SELECT name FROM products WHERE name SIMILAR TO 'стол|стул';
3️⃣ Проверка сложного формата:
- Найти номера телефонов в формате +7(XXX)XXX-XX-XX:
SELECT phone FROM users WHERE phone SIMILAR TO '\+7\(___\)___-__-__';
Здесь мы используем _ для обозначения любого цифрового символа и экранируем литеральные скобки \( и \), а также знак \+ с помощью обратной косой черты.
4️⃣ Использование повторов и диапазонов:
- Найти коды товаров, которые состоят из 3-5 цифр, а затем одной или двух букв:
SELECT code FROM products WHERE code SIMILAR TO '[0-9]{3,5}[A-Z]{1,2}';
Главный недостаток SIMILAR TO — это его производительность. Он реализован поверх механизма регулярных выражений, но менее эффективен, чем как нативный LIKE, так и часто даже операторы ~.
Когда использовать SIMILAR TO?
✅ Используйте LIKE для простейшего поиска подстрок. Это самый быстрый и стандартный вариант;
✅ Используйте SIMILAR TO в исключительных случаях, когда шаблон слишком сложен для LIKE (требует повторов, выбора или групп), но вам критически важно, чтобы шаблон описывал всю строку, и вы не хотите разбираться с полным синтаксисом регулярных выражений;
✅ Используйте операторы ~ (регулярные выражения) для самых сложных и гибких задач поиска, когда мощь SIMILAR TO уже недостаточна, или когда вам нужно искать не полное совпадение, а подстроку.
В большинстве реальных сценариев LIKE и полноценные регулярные выражения ~ покрывают все потребности. SIMILAR TO остается нишевым оператором, полезным в специфических ситуациях, но его стоит иметь в виду как еще один инструмент в вашем арсенале SQL.
На этом все! До связи!
#queriesSELECT * FROM users WHERE name LIKE 'Анн%';
Найти всех, у кого в имени есть "маш":
SELECT * FROM users WHERE name ILIKE '%маш%'; -- ILIKE найдет и "Маша", и "МАША"
Найти пользователей с именами из 5 букв, которые начинаются на "А" и заканчиваются на "й":
SELECT * FROM users WHERE name LIKE 'А_____й';
Найти товары с артикулом, по маске: 123-любой_текст-456:
SELECT * FROM products WHERE article LIKE '123-%-456';
Найти все email в домене yandex (без учета регистра):
SELECT * FROM users WHERE email ILIKE '%@yandex.ru';
💡 Советы и лучшие практики
1️⃣ Производительность: Запросы с LIKE могут быть медленными, если шаблон начинается с % (например, '%abc'), потому что СУБД не может использовать обычный индекс (ей придётся сканировать всю таблицу — seq scan). Для таких запросов лучше подходят GIN-индексы с опцией gin_trgm_ops (триграммные индексы), которые PostgreSQL умеет использовать для поиска по подстроке;
2️⃣ Экранирование: Если вам нужно найти сам символ % или _, его нужно экранировать с помощью символа \ (обратный слеш) или указав другой escape-символ с помощью конструкции ESCAPE.
Найти значения, содержащие символ "_":
SELECT * FROM table WHERE name LIKE '%\_%' ESCAPE '\';
3️⃣ Когда что использовать?
LIKE/ILIKE — ваш выбор, когда шаблон простой: поиск по началу, концу строки или наличию подстроки.
Регулярные выражения - берите в бой, когда шаблон сложный: "найти все слова, начинающиеся на гласную, затем идущие 3 цифры, а потом знак препинания", например;
4️⃣ Читаемость: Запрос с LIKE 'ABC%' всегда будет более читаемым и очевидным для другого разработчика, чем его аналог с регулярным выражением ~ '^ABC'.
Вывод: LIKE и ILIKE - это простые и эффективные инструменты для 80% задач по поиску текста. Не забывайте о них, прежде чем брать в руки тяжёлую артиллерию регулярных выражений. О регулярках в PostgreSQL мы поговорим в будущих постах.
На этом все! До связи!
#pgbase #queriesDO [ LANGUAGE lang_name ] $$
DECLARE
-- объявление переменных (опционально)
BEGIN
-- исполняемый код
END;
$$;
Здесь lang_name необходим в том случае, если вы используете язык программирования, отличный от PL/pgSQL. Язык должен быть установлен в PostgreSQL.
Основные характеристики команды:
1️⃣ Анонимность. Блок кода не сохраняется в базе данных и выполняется только один раз при вызове.
2️⃣ Одноразовое выполнение. Идеально для разовых задач, миграций, тестирования.
3️⃣ Не принимает параметры. В отличие от функций, не может принимать входные параметры.
4️⃣ Не возвращает результат (void).
Простой пример:
DO $$
BEGIN
RAISE NOTICE 'Текущее время: %', NOW();
END;
$$;
Пример с объявлением переменных:
DO $$
DECLARE
user_count INTEGER;
avg_id NUMERIC;
BEGIN
SELECT COUNT(*) INTO user_count FROM users;
RAISE NOTICE 'Количество пользователей: %', user_count;
END;
$$;
Практическое использование.
1️⃣ Массовое обновление данных:
DO $$
BEGIN
UPDATE products
SET price = price * 1.1
WHERE category_id = 5;
RAISE NOTICE 'Цены обновлены для категории 5';
END;
$$;
3️⃣ Валидация данных:
DO $$
DECLARE
invalid_records INTEGER;
BEGIN
SELECT COUNT(*) INTO invalid_records
FROM orders
WHERE order_date > CURRENT_DATE;
IF invalid_records > 0 THEN
RAISE EXCEPTION 'Найдено % записей с будущей датой заказа', invalid_records;
END IF;
END;
$$;
3️⃣ Сложная логика с обработкой ошибок:
DO $$
BEGIN
BEGIN
INSERT INTO audit_log (action, table_name, user_id)
VALUES ('UPDATE', 'products', current_user);
UPDATE products SET last_updated = NOW();
RAISE NOTICE 'Операция выполнена успешно';
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Произошла ошибка: %', SQLERRM;
ROLLBACK;
END;
END;
$$;
Особенности и ограничения:
1️⃣ Нельзя использовать в выражениях. В отличие от функций, результат DO нельзя использовать в SELECT;
2️⃣ Нет возвращаемого значения.
3️⃣ Временное существование. Код выполняется и сразу "забывается" сервером.
4️⃣ Поддержка транзакций. Весь блок выполняется в одной транзакции, если не указано иное.
Best Practices:
✅ Используйте для разовых операций;
✅ Для сложной логики, требующей повторного использования, создавайте функции;
✅ Всегда обрабатывайте возможные исключения;
✅ Используйте RAISE NOTICE для отладки;
✅ Для длительных операций добавляйте индикацию прогресса.
На этом все! До связи!
#pl/pgsqlСтатистика
Отзывы канала
всего 5 отзывов
- Добавлен: Сначала новые
- Добавлен: Сначала старые
- Оценка: По убыванию
- Оценка: По возрастанию
Каталог Телеграм-каналов для нативных размещений
Postgres Guru | Базы данных — это Telegam канал в категории «Интернет технологии», который предлагает эффективные форматы для размещения рекламных постов в Телеграмме. Количество подписчиков канала в 2.8K и качественный контент помогают брендам привлекать внимание аудитории и увеличивать охват. Рейтинг канала составляет 21.5, количество отзывов – 5, со средней оценкой 5.0.
Вы можете запустить рекламную кампанию через сервис Telega.in, выбрав удобный формат размещения. Платформа обеспечивает прозрачные условия сотрудничества и предоставляет детальную аналитику. Стоимость размещения составляет 2797.2 ₽, а за 43 выполненных заявок канал зарекомендовал себя как надежный партнер для рекламы в TG. Размещайте интеграции уже сегодня и привлекайте новых клиентов вместе с Telega.in!
Вы снова сможете добавить каналы в корзину из каталога
Комментарий