SQL Ready | Базы Данных
Авторский канал про Базы Данных и SQL Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_c РКН: https://clck.ru/3QREBc Реклама на бирже: https://telega.in/c/sql_ready
Показати більше📈 Аналітичний огляд Telegram-каналу SQL Ready | Базы Данных
Канал SQL Ready | Базы Данных (@sql_ready) у мовному сегменті Російська є активним учасником. На даний момент спільнота об'єднує 16 616 підписників, посідаючи 7 640 місце в категорії Технології та додатки та 39 717 місце у регіоні Росія.
📊 Показники аудиторії та динаміка
З моменту свого створення невідомо, проект продемонстрував стрімке зростання, зібравши аудиторію у 16 616 підписників.
За останніми даними від 01 вересня, 2026, канал демонструє стабільну активність. Хоча за останні 30 днів спостерігається зміна кількості учасників на 1 233, а за останні 24 години на -2, загальне охоплення залишається високим.
- Статус верифікації: Не верифікований
- Рівень залученості (ER): Середній показник залученості аудиторії становить 12.49%. Протягом перших 24 годин після публікації контент зазвичай збирає 6.17% реакцій від загальної кількості підписників.
- Охоплення публікацій: В середньому кожен допис отримує 2 075 переглядів. Протягом першої доби публікація в середньому набирає 1 025 переглядів.
- Реакції та взаємодія: Аудиторія активно підтримує контент: середня кількість реакцій на один пост – 23.
- Тематичні інтереси: Контент зосереджений навколо ключових тем, таких як sql, строка, users, индекс, user_id.
📝 Опис та контентна політика
Автор описує ресурс як майданчик для висловлення суб'єктивної думки:
“Авторский канал про Базы Данных и SQL
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!
Автор: @energy_c
РКН: https://clck.ru/3QREBc
Реклама на бирже: https://telega.in/c/sql_ready”
Завдяки високій частоті оновлень (останні дані отримано 02 вересня, 2026), канал підтримує актуальність та високий рівень охоплення публікацій. Аналітика показує, що аудиторія активно взаємодіє з контентом, що робить його важливою точкою впливу в категорії Технології та додатки.
SELECT *
FROM products
ORDER BY RANDOM()
LIMIT 10;
На первый взгляд всё отлично: перемешали строки и взяли первые 10. Но под капотом всё не так красиво. Обычно база читает много строк, а часто вообще всю таблицу, затем вычисляет случайное значение для каждой строки, сортирует результат и только после этого применяет LIMIT.
То есть даже если нужна всего одна строка:
SELECT *
FROM products
ORDER BY RANDOM()
LIMIT 1;
на большой таблице это всё равно может оказаться тяжёлым запросом. Если строк миллионы, начинаются проблемы с CPU, памятью, сортировками и latency. Индексы здесь обычно не помогают.
Один из более дешёвых вариантов — случайный выбор через id:
SELECT *
FROM products
WHERE id >= 1 + FLOOR(RANDOM() * (SELECT MAX(id) FROM products))::int
ORDER BY id
LIMIT 1;
Этот вариант уже может использовать индекс по id. Но есть нюанс: если после удалений в id много дырок, распределение будет неидеальным.
Например:
id
1
2
3
10000
10001
В таком случае чаще будет выпадать первая существующая строка после большого разрыва — здесь это 10000. То есть выборка получается смещённой.
Ещё один вариант — случайный OFFSET:
SELECT *
FROM products
OFFSET FLOOR(RANDOM() * (SELECT COUNT(*) FROM products))::int
LIMIT 1;
Звучит неплохо. Но у OFFSET тоже есть проблема: чем больше offset, тем больше строк базе придётся пропустить. Плюс в PostgreSQL COUNT(*) на большой таблице тоже может быть дорогой операцией.
В PostgreSQL есть ещё TABLESAMPLE:
SELECT *
FROM products TABLESAMPLE SYSTEM (1);
Или:
SELECT *
FROM products TABLESAMPLE BERNOULLI (1);
На больших таблицах это часто работает заметно быстрее. Но важно понимать: TABLESAMPLE выбирает примерный процент таблицы, а не конкретное количество строк.
Если нужно, например, 10 строк, обычно добавляют LIMIT:
SELECT *
FROM products TABLESAMPLE SYSTEM (1)
LIMIT 10;
Но и тут есть нюанс: если sample слишком маленький, запрос может вернуть меньше 10 строк.
И здесь тоже есть компромисс между скоростью и качеством случайной выборки. SYSTEM работает быстрее, но выбирает данные блоками, поэтому распределение менее равномерное. BERNOULLI ближе к построчной случайной выборке, но обычно дороже. В общем, мысль простая:
ORDER BY RANDOM()
выглядит красиво и удобно. Но на больших таблицах это один из тех запросов, которые начинают стоить очень дорого.
🔥 Именно такие простые запросы часто становятся причиной деградации производительности в продакшн.
➡️ SQL Ready | #практикаINSERT или UPDATE. PostgreSQL умеет превратить JSON-массив в обычную табличную выборку.
SELECT *
FROM jsonb_to_recordset($1::jsonb)
AS x(id bigint, amount numeric);
На вход можно передать JSON вида [{"id":1,"amount":500},{"id":2,"amount":900}], а на выходе получить нормальные типизированные строки.
INSERT INTO payments (id, amount)
SELECT id, amount
FROM jsonb_to_recordset($1::jsonb)
AS x(id bigint, amount numeric);
Это особенно удобно для пакетных загрузок, импорта данных, обработки вебхуков и синхронизации с внешними сервисами.
UPDATE payments p
SET amount = x.amount
FROM jsonb_to_recordset($1::jsonb)
AS x(id bigint, amount numeric)
WHERE p.id = x.id;
Главный плюс в том, что приложение передаёт один JSON-параметр, а вся пакетная обработка происходит внутри базы одним запросом.
🔥 Такой приём убирает циклы в коде, уменьшает количество запросов к базе и делает массовые операции намного чище.
➡️ SQL Ready | #советlost update. Например, есть таблица:
accounts(id, balance)
Текущий баланс:
id | balance
1 | 1000
Два запроса одновременно читают баланс:
SELECT balance
FROM accounts
WHERE id = 1;
Оба получают: 1000. Дальше: первый процесс хочет списать 100; второй — 200. Первый считает: 1000 - 100 = 900. Второй: 1000 - 200 = 800.
После этого выполняются:
UPDATE accounts
SET balance = 900
WHERE id = 1;
а также:
UPDATE accounts
SET balance = 800
WHERE id = 1;
В итоге финальный баланс: 800, хотя математически должен быть: 700, одно обновление потерялось. Это и есть lost update.
И самое неприятное — такое может происходить даже внутри транзакций. Всё зависит от уровня изоляции, паттерна обновления и механики блокировок конкретной СУБД.
Очень частая ошибка выглядит так:
BEGIN;
SELECT balance
FROM accounts
WHERE id = 1;
-- вычисления в приложении
UPDATE accounts
SET balance = :new_balance
WHERE id = 1;
COMMIT;
Проблема в том, что между SELECT и UPDATE другая транзакция может изменить строку.
Один из самых надёжных вариантов — атомарное обновление:
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
Теперь вычисление происходит внутри самого UPDATE.
СУБД выполняет обновление на основе актуального значения строки и использует необходимые механизмы блокировок для корректной синхронизации конкурентных изменений. Это намного безопаснее.
Ещё один вариант — pessimistic locking:
SELECT balance
FROM accounts
WHERE id = 1
FOR UPDATE;
FOR UPDATE ставит row-level lock. Пока транзакция не завершится, другая транзакция не сможет изменить эту строку или получить несовместимую блокировку на неё.
Но здесь есть trade-off, чем больше блокировок: тем выше contention; тем ниже concurrency; тем выше риск deadlock.
Ещё один подход — optimistic locking. Например:
accounts(id, balance, version)
Чтение:
SELECT balance, version
FROM accounts
WHERE id = 1;
Обновление:
UPDATE accounts
SET
balance = :new_balance,
version = version + 1
WHERE
id = 1
AND version = 5;
Если другая транзакция уже изменила строку — UPDATE затронет 0 строк. Приложение понимает: данные устарели, нужно перечитать и повторить операцию. Ещё важно понимать: READ COMMITTED, REPEATABLE READ, SERIALIZABLE ведут себя по-разному в разных СУБД.
Например, PostgreSQL и MySQL (InnoDB) используют разные механизмы MVCC и имеют различия в поведении блокировок и уровней изоляции.
🔥 Главное правило, если логика выглядит как: прочитал значение — изменил в приложении — записал обратно, то всегда стоит проверять, не появляется ли lost update при параллельных запросах.
➡️ SQL Ready | #практикаLoading … ██████████████] 99%Роскомнадзору воспользовался карт-бланшем на блокировку, а «белые списки» сайтов внедрены уже во всех регионах. И гайки будут закручиваться только сильнее. Чтобы в одночасье не лишиться доступа к свободному Интернету, просто сохрани Only Hack. Тут профессиональный хакер делится фишками, с которыми доступ к глобальной сети у тебя будет даже в случае ядерного апокалипсиса. Не жди момента «Х». Перестрахуйся подпиской.
Оставляю ссылочку: GitHub 📱➡️ SQL Ready | #репозиторий
FOR UPDATE ещё может не быть.
SELECT pg_advisory_xact_lock(10, 42);
pg_advisory_xact_lock создаёт транзакционную пользовательскую блокировку. Первый аргумент удобно использовать как namespace, второй — как id объекта.
SELECT pg_advisory_xact_lock(20, user_id);
Пока транзакция не завершится, другой процесс с тем же ключом будет ждать. После COMMIT или ROLLBACK блокировка снимается автоматически.
SELECT pg_try_advisory_xact_lock(20, user_id);
Если ждать нельзя, используй pg_try_advisory_xact_lock: он сразу вернёт true или false, и приложение сможет аккуратно пропустить задачу.
BEGIN;
SELECT pg_advisory_xact_lock(30, 123);
UPDATE invoices SET status = 'paid' WHERE id = 123;
COMMIT;
advisory lock не блокирует таблицу и не заменяет ограничения БД. Это кооперативная блокировка, поэтому все участники должны использовать один и тот же ключ.
🔥 Полезно для идемпотентных операций, генерации документов, биллинга, обработки вебхуков и любых мест, где один и тот же объект нельзя обрабатывать параллельно.
➡️ SQL Ready | #советОставляю ссылочку: GitHub 📱➡️ SQL Ready | #репозиторий
users(id, name)
orders(id, user_id, amount)
Сначала приложение получает пользователей:
SELECT
id,
name
FROM users;
Допустим, запрос вернул 1000 пользователей. Дальше приложение начинает отдельно загружать заказы для каждого пользователя:
SELECT
id,
user_id,
amount
FROM orders
WHERE user_id = ?;
И этот запрос выполняется уже 1000 раз. То есть итоговая схема выглядит так: 1 запрос на получение users, N запросов на получение orders. Это и есть классическая N+1 problem.
В ORM это обычно выглядит примерно так:
users = User.objects.all()
for user in users:
orders = list(user.order_set.all())
print(orders)
Внешне код выглядит абсолютно нормально, но внутри ORM может выполнять отдельный SELECT для каждого user.order_set.all().
На маленьких объемах данных проблема почти незаметна. Но на продакшене начинают быстро расти: latency, network overhead, нагрузка на connection pool, время ответа API, нагрузка на БД.
Особенно неприятно это проявляется при pagination, background jobs и high-load API. Обычно данные эффективнее загружать набором.
Например, через JOIN:
SELECT
u.id,
u.name,
o.id,
o.amount
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id;
Либо через batch loading:
SELECT
id,
user_id,
amount
FROM orders
WHERE user_id IN (?, ?, ?, ...);
Во многих случаях batch loading даже эффективнее огромного JOIN, потому что JOIN может раздувать result set и создавать большое количество дублирующихся строк при one-to-many связях.
Поэтому современные ORM обычно уже имеют встроенные механизмы борьбы с N+1.
Например, в Django:
User.objects.prefetch_related("order_set")
User.objects.select_related("profile")
prefetch_related() обычно используется для reverse FK и many-to-many; select_related() — для FK и OneToOne.
В SQLAlchemy:
select(User).options(
selectinload(User.orders)
)
Отдельная проблема — nested N+1:
users
→ orders
→ payments
→ items
И приложение внезапно начинает выполнять уже сотни, тысячи или даже десятки тысяч SQL-запросов. Самое опасное — проблема часто долго остается незаметной, пока объем данных не вырастает.
🔥 Если внутри цикла потенциально выполняется SQL-запрос — почти всегда стоит проверить код на N+1. Именно поэтому profiling SQL-запросов и понимание того, как ORM реально работает с базой, критично для продакшн backend-разработки.
➡️ SQL Ready | #практикаCTE прямо внутрь основного запроса.
WITH data AS (
SELECT *
FROM orders
)
SELECT *
FROM data
WHERE user_id = 42;
Такой WITH может вообще исчезнуть из плана выполнения, потому что PostgreSQL встроит его обратно в запрос. Но иногда это плохо.
Например, если внутри CTE дорогой расчёт, который нельзя выполнять повторно. Тут появляется малоизвестная фича:
WITH expensive AS MATERIALIZED (
SELECT *
FROM huge_events
WHERE created_at >= now() - interval '1 day'
)
SELECT COUNT(*)
FROM expensive;
MATERIALIZED заставляет PostgreSQL сначала физически вычислить CTE, а потом использовать результат дальше.
А обратная фича:
WITH data AS NOT MATERIALIZED (
SELECT *
FROM orders
)
SELECT *
FROM data
WHERE user_id = 42;
наоборот подсказывает оптимизатору агрессивно встраивать CTE обратно в запрос.
🔥 Одна и та же CTE с MATERIALIZED и без него иногда отличается по производительности в десятки раз на больших объёмах данных.
➡️ SQL Ready | #совет🥩 Сгенерируй готовую презентацию по одной ссылке [открыть] 📇 Собери досье на любого человека по никнейму [открыть] 🕵️♂️ Открой сборник детективных загадок и разгадай их [открыть] 🎧 Получи бесконечный плейлист под любой вайб [открыть] 💻 Создай любой сайт со смартфона за 10 минут [открыть]На канале «Будущее сегодня» публикуют все последние новости из мира технологий для русскоязычной аудитории 🌐 📁 Собрано 99+ самых полезных и интересных сервисов, сохрани: @futurenow
