Не верь на слово, проверь под давлением.
Не верь на слово, проверь под давлением.

Предисловие

Оптимизировать запрос в вакууме — просто. Но как он поведет себя, когда десятки таких же запросов одновременно борются за ресурсы?

Методология исследования

Тестовая среда и инструменты:

Конфигурационные параметры СУБД
track_io_timing = 'on'
listen_addresses = '0.0.0.0'
logging_collector = 'on'
log_directory = '/log/pg_log'
log_destination = 'stderr'
log_rotation_size = '0'
log_rotation_age = '1d'
log_filename = 'postgresql-%u.log'
log_line_prefix = '%m| %d| %a| %u| %h| %p| %e| '
log_truncate_on_rotation = 'on'
log_checkpoints = 'on'
archive_mode = 'on'
archive_command = 'true'
archive_timeout = '30min'
checkpoint_timeout = '15min'
checkpoint_warning = '60'
checkpoint_completion_target = '0.9'
min_wal_size = '2GB'
max_wal_size = '8GB'
synchronous_commit = 'on'
wal_compression = 'on'
random_page_cost = '1.1'
effective_io_concurrency = '300'
wal_sender_timeout = '0'
autovacuum_naptime = '1s'
autovacuum_vacuum_scale_factor = '0.01'
autovacuum_analyze_scale_factor = '0.005'
autovacuum_vacuum_cost_delay = '2ms'
autovacuum_max_workers = '4'
autovacuum_work_mem = '256MB'
vacuum_cost_limit = '4000'
bgwriter_delay = '10ms'
bgwriter_lru_multiplier = '4'
bgwriter_lru_maxpages = '400'
max_locks_per_transaction = '256'
max_pred_locks_per_transaction = '256'
shared_buffers = '1919MB'
effective_cache_size = '5757MB'
work_mem = '14MB'
temp_buffers = '14MB'
maintenance_work_mem = '479MB'
max_worker_processes = '8'
max_parallel_workers = '8'
max_parallel_workers_per_gather = '4'
idle_in_transaction_session_timeout = '1h'
statement_timeout = '8h'
pg_stat_statements.track_utility = 'off'
max_parallel_maintenance_workers = '4'
hash_mem_multiplier = '2'
autovacuum_vacuum_insert_scale_factor = '0.01'
shared_preload_libraries = 'pg_stat_statements , pg_wait_sampling'
commit_delay = '1000'
log_autovacuum_min_duration = '0'
wipe_file_on_delete = 'on'
wipe_heaptuple_on_delete = 'on'
wipe_mem_on_free = 'on'
wipe_memctx_on_free = 'on'
wipe_xlog_on_free = 'on'
log_connections = 'on'
log_disconnections = 'on'
pg_stat_statements.track = 'all'
max_connections = '1000'

План нагрузочного тестирования (param.conf)

# НАСТРОЙКИ НАГРУЗОЧНОГО ТЕСТИРОВАНИЯ
# Максимальная нагрузка
finish_load = 20
# Тестовая БД
testdb = demo
# Веса сценариев
scenario1 = 1.0

Нагрузка на СУБД

Тестовый запрос, используемый в качестве baseline : LEFT JOIN

Запрос демонстрирует:

  1. LEFT JOIN bookings → tickets
    Все бронирования, даже без привязанных билетов

  2. LEFT JOIN tickets → segments
    Все билеты, даже без перелётов

  3. LEFT JOIN segments → flights
    Все перелёты, даже без информации о рейсах

  4. LEFT JOIN segments → boarding_passes
    Все перелёты, даже без посадочных талонов

  5. Выбирает случайную дату из существующих бронирований

  6. Гарантирует, что период полностью входит в доступный диапазон дат

Такой запрос может быть полезен для анализа:

  • Неполных бронирований

  • Билетов без привязки к рейсам

  • Рейсов без процедуры посадки

  • Статистики по незавершённым операциям

Таблицы, используемые в тестовом запросе
demo=# \d bookings
                    Unlogged table "bookings.bookings"
    Column    |           Type           | Collation | Nullable | Default
--------------+--------------------------+-----------+----------+---------
 book_ref     | character(6)             |           | not null |
 book_date    | timestamp with time zone |           | not null |
 total_amount | numeric(10,2)            |           | not null |
Indexes:
    "bookings_pkey" PRIMARY KEY, btree (book_ref)
    "idx_bookings_book_date" btree (book_date)
Referenced by:
    TABLE "tickets" CONSTRAINT "tickets_book_ref_fkey" FOREIGN KEY (book_ref) REFERENCES bookings(book_ref)

demo=# \d tickets
               Unlogged table "bookings.tickets"
     Column     |     Type     | Collation | Nullable | Default
----------------+--------------+-----------+----------+---------
 ticket_no      | text         |           | not null |
 book_ref       | character(6) |           | not null |
 passenger_id   | text         |           | not null |
 passenger_name | text         |           | not null |
 outbound       | boolean      |           | not null |
Indexes:
    "tickets_pkey" PRIMARY KEY, btree (ticket_no)
    "idx_tickets_book_ref" btree (book_ref)
    "tickets_book_ref_passenger_id_outbound_key" UNIQUE CONSTRAINT, btree (book_ref, passenger_id, outbound)
Foreign-key constraints:
    "tickets_book_ref_fkey" FOREIGN KEY (book_ref) REFERENCES bookings(book_ref)
Referenced by:
    TABLE "segments" CONSTRAINT "segments_ticket_no_fkey" FOREIGN KEY (ticket_no) REFERENCES tickets(ticket_no)

demo=#  \d segments
                Unlogged table "bookings.segments"
     Column      |     Type      | Collation | Nullable | Default
-----------------+---------------+-----------+----------+---------
 ticket_no       | text          |           | not null |
 flight_id       | integer       |           | not null |
 fare_conditions | text          |           | not null |
 price           | numeric(10,2) |           | not null |
Indexes:
    "segments_pkey" PRIMARY KEY, btree (ticket_no, flight_id)
    "idx_segments_flight_id" btree (flight_id)
    "idx_segments_flight_id_fare_conditions" btree (flight_id, fare_conditions)
    "idx_segments_ticket_no" btree (ticket_no)
    "segments_flight_id_idx" btree (flight_id)
Check constraints:
    "segments_fare_conditions_check" CHECK (fare_conditions = ANY (ARRAY['Economy'::text, 'Comfort'::text, 'Business'::text]))
    "segments_price_check" CHECK (price >= 0::numeric)
Foreign-key constraints:
    "segments_flight_id_fkey" FOREIGN KEY (flight_id) REFERENCES flights(flight_id)
    "segments_ticket_no_fkey" FOREIGN KEY (ticket_no) REFERENCES tickets(ticket_no)
Referenced by:
    TABLE "boarding_passes" CONSTRAINT "boarding_passes_ticket_no_flight_id_fkey" FOREIGN KEY (ticket_no, flight_id) REFERENCES segments(ticket_no, flight_id)

demo=# \d boarding_passes
                 Unlogged table "bookings.boarding_passes"
    Column     |           Type           | Collation | Nullable | Default
---------------+--------------------------+-----------+----------+---------
 ticket_no     | text                     |           | not null |
 flight_id     | integer                  |           | not null |
 seat_no       | text                     |           | not null |
 boarding_no   | integer                  |           |          |
 boarding_time | timestamp with time zone |           |          |
Indexes:
    "boarding_passes_pkey" PRIMARY KEY, btree (ticket_no, flight_id)
    "boarding_passes_flight_id_boarding_no_key" UNIQUE CONSTRAINT, btree (flight_id, boarding_no)
    "boarding_passes_flight_id_boarding_time" btree (flight_id, boarding_time)
    "boarding_passes_flight_id_seat_no_key" UNIQUE CONSTRAINT, btree (flight_id, seat_no)
Foreign-key constraints:
    "boarding_passes_ticket_no_flight_id_fkey" FOREIGN KEY (ticket_no, flight_id) REFERENCES segments(ticket_no, flight_id)

demo=# \d flights
                                  Unlogged table "bookings.flights"
       Column        |           Type           | Collation | Nullable |           Default
---------------------+--------------------------+-----------+----------+------------------------------
 flight_id           | integer                  |           | not null | generated always as identity
 route_no            | text                     |           | not null |
 status              | text                     |           | not null |
 scheduled_departure | timestamp with time zone |           | not null |
 scheduled_arrival   | timestamp with time zone |           | not null |
 actual_departure    | timestamp with time zone |           |          |
 actual_arrival      | timestamp with time zone |           |          |
Indexes:
    "flights_pkey" PRIMARY KEY, btree (flight_id)
    "flights_route_no_scheduled_departure_key" UNIQUE CONSTRAINT, btree (route_no, scheduled_departure)
    "idx_flights_route_no" btree (route_no)
    "idx_flights_scheduled_departure" btree (scheduled_departure DESC)
Check constraints:
    "flight_actual_check" CHECK (actual_arrival IS NULL OR actual_departure IS NOT NULL AND actual_arrival IS NOT NULL AND actual_arrival > actual_departure)
    "flight_scheduled_check" CHECK (scheduled_arrival > scheduled_departure)
    "flight_status_check" CHECK (status = ANY (ARRAY['Scheduled'::text, 'On Time'::text, 'Delayed'::text, 'Boarding'::text, 'Departed'::text, 'Arrived'::text, 'Cancelled'::text]))
Referenced by:
    TABLE "segments" CONSTRAINT "segments_flight_id_fkey" FOREIGN KEY (flight_id) REFERENCES flights(flight_id)
Тестовый запрос - LEFT JOIN
WITH random_period AS (
    SELECT 
        start_date,
        start_date + INTERVAL '30 days' AS end_date
    FROM (
        SELECT 
            book_date AS start_date
        FROM bookings
        WHERE book_date <= (SELECT MAX(book_date) FROM bookings) - INTERVAL '30 days'
        ORDER BY RANDOM()
        LIMIT 1
    ) AS random_date
)
SELECT 
    b.book_ref,
    b.book_date,
    t.ticket_no,
    t.passenger_name,
    s.flight_id,
    f.status,
    f.scheduled_departure,
    f.actual_departure,
    bp.seat_no,
    bp.boarding_no,
    rp.start_date AS period_start,
    rp.end_date AS period_end
FROM random_period rp
CROSS JOIN LATERAL (
    SELECT *
    FROM bookings b
    WHERE b.book_date BETWEEN rp.start_date AND rp.end_date
) b
LEFT JOIN tickets t ON b.book_ref = t.book_ref
LEFT JOIN segments s ON t.ticket_no = s.ticket_no
LEFT JOIN flights f ON s.flight_id = f.flight_id
LEFT JOIN boarding_passes bp ON 
    s.ticket_no = bp.ticket_no 
    AND s.flight_id = bp.flight_id
ORDER BY b.book_date, t.ticket_no;
План выполнения тестового запроса — LEFT JOIN
Sort  (cost=2544355.52..2550077.32 rows=2288717 width=93) (actual time=58778.563..59050.999 rows=1146771 loops=1)
   Sort Key: b.book_date, t.ticket_no
   Sort Method: external merge  Disk: 121832kB
   CTE random_period
     ->  Subquery Scan on random_date  (cost=66429.70..66429.71 rows=1 width=16) (actual time=2883.937..2883.945 rows=1 loops=1)
           ->  Limit  (cost=66429.70..66429.70 rows=1 width=16) (actual time=2883.919..2883.925 rows=1 loops=1)
                 InitPlan 2
                   ->  Result  (cost=0.45..0.46 rows=1 width=8) (actual time=1.994..1.996 rows=1 loops=1)
                         InitPlan 1
                           ->  Limit  (cost=0.43..0.45 rows=1 width=8) (actual time=1.989..1.991 rows=1 loops=1)
                                 ->  Index Only Scan Backward using idx_bookings_book_date on bookings  (cost=0.43..128154.91 rows=7113192 width=8) (actual time=1.988..1.989 rows=1 loops=1)
                                       Heap Fetches: 0
                 ->  Sort  (cost=66429.24..72356.90 rows=2371064 width=16) (actual time=2883.917..2883.918 rows=1 loops=1)
                       Sort Key: (random())
                       Sort Method: top-N heapsort  Memory: 25kB
                       ->  Index Only Scan using idx_bookings_book_date on bookings bookings_1  (cost=0.43..54573.91 rows=2371064 width=16) (actual time=2.183..1714.099 rows=6894590 loops=1)
                             Index Cond: (book_date <= ((InitPlan 2).col1 - '30 days'::interval))
                             Heap Fetches: 0
   ->  Hash Left Join  (cost=685482.92..2046557.85 rows=2288717 width=93) (actual time=49378.694..57307.629 rows=1146771 loops=1)
         Hash Cond: (s.flight_id = f.flight_id)
         ->  Merge Right Join  (cost=681880.13..2036946.95 rows=2288717 width=69) (actual time=49326.107..56627.660 rows=1146771 loops=1)
               Merge Cond: (s.ticket_no = t.ticket_no)
               ->  Merge Left Join  (cost=1.12..1264914.46 rows=20598448 width=25) (actual time=3.919..32375.016 rows=6564405 loops=1)
                     Merge Cond: ((s.ticket_no = bp.ticket_no) AND (s.flight_id = bp.flight_id))
                     ->  Index Only Scan using segments_pkey on segments s  (cost=0.56..424624.50 rows=20598448 width=18) (actual time=3.875..1950.364 rows=6564405 loops=1)
                           Heap Fetches: 0
                     ->  Index Scan using boarding_passes_pkey on boarding_passes bp  (cost=0.56..637941.24 rows=19870556 width=25) (actual time=0.033..25169.883 rows=6564405 loops=1)
               ->  Materialize  (cost=681879.01..690532.20 rows=1730639 width=58) (actual time=21308.253..22022.391 rows=1146771 loops=1)
                     ->  Sort  (cost=681879.01..686205.61 rows=1730639 width=58) (actual time=21308.245..21812.491 rows=862459 loops=1)
                           Sort Key: t.ticket_no
                           Sort Method: external merge  Disk: 58600kB
                           ->  Nested Loop Left Join  (cost=0.87..464448.40 rows=1730639 width=58) (actual time=2886.101..18909.459 rows=862459 loops=1)
                                 ->  Nested Loop  (cost=0.43..31711.08 rows=790355 width=31) (actual time=2884.013..3268.567 rows=397632 loops=1)
                                       ->  CTE Scan on random_period rp  (cost=0.00..0.02 rows=1 width=16) (actual time=2883.941..2883.946 rows=1 loops=1)
                                       ->  Index Scan using idx_bookings_book_date on bookings b  (cost=0.43..23807.51 rows=790355 width=15) (actual time=0.063..308.659 rows=397632 loops=1)
                                             Index Cond: ((book_date >= rp.start_date) AND (book_date <= rp.end_date))
                                 ->  Index Scan using idx_tickets_book_ref on tickets t  (cost=0.43..0.52 rows=3 width=34) (actual time=0.038..0.038 rows=2 loops=397632)
                                       Index Cond: (book_ref = b.book_ref)
         ->  Hash  (cost=2247.35..2247.35 rows=108435 width=28) (actual time=52.383..52.385 rows=108435 loops=1)
               Buckets: 131072  Batches: 1  Memory Usage: 7377kB
               ->  Seq Scan on flights f  (cost=0.00..2247.35 rows=108435 width=28) (actual time=0.024..22.614 rows=108435 loops=1)
 Planning Time: 37.456 ms
 Execution Time: 59151.413 ms

Эксперимент-1 : вариант с использованием EXISTS

Эксперимент-2 : вариант с оптимизацией структуры запроса (TUNING)

Эксперимент-3 : Частичная агрегация

Эксперимент-4: Условие WHERE

Тестовый запрос — WHERE
WITH random_period AS (
    SELECT 
        book_date AS start_date,
        book_date + INTERVAL '30 days' AS end_date
    FROM bookings 
    ORDER BY RANDOM() 
    LIMIT 1
)
SELECT 
    b.book_ref,
    b.book_date,
    t.ticket_no,
    t.passenger_name,
    s.flight_id,
    f.status,
    f.scheduled_departure,
    f.actual_departure,
    bp.seat_no,
    bp.boarding_no
FROM bookings b
CROSS JOIN random_period rp
LEFT JOIN tickets t ON b.book_ref = t.book_ref
LEFT JOIN segments s ON t.ticket_no = s.ticket_no
LEFT JOIN flights f ON s.flight_id = f.flight_id
LEFT JOIN boarding_passes bp ON s.ticket_no = bp.ticket_no AND s.flight_id = bp.flight_id
WHERE b.book_date BETWEEN rp.start_date AND rp.end_date
  AND (f.status IS NULL OR f.status IN ('Scheduled', 'On Time', 'Departed')) -- Фильтр по статусу
ORDER BY b.book_date, t.ticket_no;
План выполнения тестового запроса — WHERE
 Sort  (cost=1445335.77..1445902.80 rows=226814 width=77) (actual time=26918.538..27291.773 rows=0 loops=1)
   Sort Key: b.book_date, t.ticket_no
   Sort Method: quicksort  Memory: 25kB
   CTE random_period
     ->  Limit  (cost=191879.84..191879.84 rows=1 width=24) (actual time=4017.386..4017.390 rows=1 loops=1)
           ->  Sort  (cost=191879.84..209662.82 rows=7113192 width=24) (actual time=4017.383..4017.385 rows=1 loops=1)
                 Sort Key: (random())
                 Sort Method: top-N heapsort  Memory: 25kB
                 ->  Seq Scan on bookings  (cost=0.00..156313.88 rows=7113192 width=24) (actual time=0.093..2706.240 rows=7113192 loops=1)
   ->  Nested Loop Left Join  (cost=381293.02..1217287.31 rows=226814 width=77) (actual time=26918.440..27291.672 rows=0 loops=1)
         ->  Nested Loop  (cost=381292.45..1081318.76 rows=226814 width=84) (actual time=26918.438..27291.667 rows=0 loops=1)
               Join Filter: ((b.book_date >= rp.start_date) AND (b.book_date <= rp.end_date))
               Rows Removed by Join Filter: 748969
               ->  CTE Scan on random_period rp  (cost=0.00..0.02 rows=1 width=16) (actual time=4017.403..4017.410 rows=1 loops=1)
               ->  Gather  (cost=381292.45..1050698.88 rows=2041324 width=84) (actual time=16712.775..23184.614 rows=748969 loops=1)
                     Workers Planned: 4
                     Workers Launched: 4
                     ->  Parallel Hash Left Join  (cost=380292.45..845566.48 rows=510331 width=84) (actual time=16677.978..22499.516 rows=149794 loops=5)
                           Hash Cond: (s.flight_id = f.flight_id)
                           Filter: ((f.status IS NULL) OR (f.status = ANY ('{Scheduled,"On Time",Departed}'::text[])))
                           Rows Removed by Filter: 3969896
                           ->  Parallel Hash Right Join  (cost=377694.29..829449.88 rows=5149612 width=60) (actual time=16654.165..20348.485 rows=4119689 loops=5)
                                 Hash Cond: (t.book_ref = b.book_ref)
                                 ->  Parallel Hash Right Join  (cost=279382.58..608356.43 rows=5149612 width=52) (actual time=9621.931..13771.242 rows=4119689 loops=5)
                                       Hash Cond: (s.ticket_no = t.ticket_no)
                                       ->  Parallel Seq Scan on segments s  (cost=0.00..224686.12 rows=5149612 width=18) (actual time=1.173..1985.149 rows=4119689 loops=5)
                                       ->  Parallel Hash  (cost=200286.37..200286.37 rows=3893937 width=34) (actual time=5974.638..5974.640 rows=3115150 loops=5)
                                             Buckets: 524288  Batches: 64  Memory Usage: 20928kB
                                             ->  Parallel Seq Scan on tickets t  (cost=0.00..200286.37 rows=3893937 width=34) (actual time=0.962..4413.744 rows=3115150 loops=5)
                                 ->  Parallel Hash  (cost=67398.98..67398.98 rows=1778298 width=15) (actual time=685.837..685.839 rows=1422638 loops=5)
                                       Buckets: 524288  Batches: 16  Memory Usage: 25088kB
                                       ->  Parallel Seq Scan on bookings b  (cost=0.00..67398.98 rows=1778298 width=15) (actual time=0.026..266.292 rows=1422638 loops=5)
                           ->  Parallel Hash  (cost=1800.85..1800.85 rows=63785 width=28) (actual time=22.852..22.853 rows=21687 loops=5)
                                 Buckets: 131072  Batches: 1  Memory Usage: 7904kB
                                 ->  Parallel Seq Scan on flights f  (cost=0.00..1800.85 rows=63785 width=28) (actual time=0.046..10.141 rows=21687 loops=5)
         ->  Index Scan using boarding_passes_pkey on boarding_passes bp  (cost=0.56..0.60 rows=1 width=25) (never executed)
               Index Cond: ((ticket_no = s.ticket_no) AND (flight_id = s.flight_id))
 Planning Time: 16.612 ms
 Execution Time: 27293.468 ms

Сравнительный анализ планов выполнения запросов

«Тестовый запрос — WHERE» имеет проблемы с производительностью:

  1. Случайный период выбирается через сортировку всей таблицы bookings (дорогая операция)

  2. Фильтрация в WHERE происходит после всех соединений, что создает промежуточные большие наборы данных

  3. Параллельное выполнение помогает, но не компенсирует неоптимальный план

Проблема — неоптимальный выбор случайного периода и поздняя фильтрация, несмотря на использование параллельного выполнения

Производительность и ожидания СУБД

Операционная скорость

рафик изменения операционной скорости в ходе нагрузочного тестирования при использовании тестового запроса "JOIN" и "TEMP TABLE"
рафик изменения операционной скорости в ходе нагрузочного тестирования при использовании тестового запроса "JOIN" и "TEMP TABLE"
График изменения относительной разницы операционной скорости для запроса "WHERE" по сравнению с тестовым запросом "JOIN"
График изменения относительной разницы операционной скорости для запроса «WHERE» по сравнению с тестовым запросом «JOIN»

Результатизменение условия WHERE приводит к деградации производительности после 15 соединений.

Ожидания СУБД

График изменения ожиданий СУБД в ходе нагрузочного тестирования при использоваении тестового запроса "JOIN" и "WHERE"
График изменения ожиданий СУБД в ходе нагрузочного тестирования при использоваении тестового запроса «JOIN» и «WHERE»
График изменения ожиданий СУБД в ходе нагрузочного тестирования при использоваении тестового запроса «JOIN» и «WHERE»
График изменения относительной разницы ожиданий СУБД для запроса «WHERE» по сравнению с тестовым запросом «JOIN»

Сравнительный анализ wait_event_type

1. wait_event_type = IO

  • Умеренные показатели (7928)

2. wait_event_type = IPC

  • Относительно низкие значения (4329)

3. wait_event_type = LWLOCK

  • Высокие значения (271) — в 2 раза больше по сравнению с «JOIN»

Больше конкуренции за блокировки, что характерно для параллельных запросов с несколькими воркерами.

4. wait_event_type = TIMEOUT

  • Повышенные значения (36)

Чаще ожидания ресурсов, что также коррелирует с параллельным выполнением.

Итог:

Проблема при изменение условия WHERE — параллельная координация, что проявляется в повышенном количестве легковесных блокировок и таймаутов.

Метрики iostat для дискового устройства, используемого файловой системой /data

Дисковая подсистема не участвует в ограничении производительности данных запросов.

Метрики vmstat

1. Процессорная нагрузка (CPU)

  • Очень высокая CPU-нагрузка (72% user + 11.6% system = ~84%)

    Нагрузка на CPU в 1.7 раза сильнее

2. Ввод/вывод (I/O)

  • Экстремальный ввод (199K блоков/сек), экстремальный вывод (226K блоков/сек)

В 2.6 раза больше операций ввода и в 46 раз больше операций вывода

3. Ожидание I/O (cpu_wa)

  • Низкое время ожидания I/O (2.4%) - диск успевает за запросами

4. Использование памяти

  • Больше свободной памяти (313 КБ), меньше кэша (6.4 МБ)

5. Системные события

  • Умеренные прерывания (8.4K/сек) и низкие переключения контекста (2.6K/сек)

Общий итог: Часть-5 «WHERE»

Для условий высокой параллельной нагрузки и конкуренции за вычислительные ресурсы, с точки зрения производительности СУБД — изменение условий WHERE — неприемлемо.

Производительность СУБД:

Производительность полностью деградирует при нагрузке выше 15 соединений.

Продолжение цикла «Анализ вариантов оптимизации ресурсоёмкого SQL-запроса»

Вариант-6: Пакетная обработка

Вариант-7: Использование покрывающих индексов

Вариант-8: Материализованное представление

Итог: сравнительный анализ вариантов оптимизации ресурсоемкого запроса

Комментарии (5)


  1. Xexa
    03.12.2025 06:00

    Оптимизировать запрос в вакууме — просто. Но как он поведет себя, когда десятки таких же запросов одновременно борются за ресурсы?

    О чём статья? О том как ведёт себя join с where на вакууме? Ой, простите, на какой-то там тестовой базе.

    Не видно причин, не видно мыслей, не видно выводов и идей как вести себя если данные иначе распределены.

    Лабораторная работа начального уровня.


  1. ChePeter
    03.12.2025 06:00

    Я правильно понимаю, что при каждом обращении к CTE random_period происходит сортировка по random всей таблицы bookings и только после выбирается первый как случайный? И так много раз

    Расскажите, пожалуйста, по подробней про эту часть

    SELECT
    book_date AS start_date
    FROM bookings
    WHERE book_date <= (SELECT MAX(book_date) FROM bookings) - INTERVAL '30 days'
    ORDER BY RANDOM()
    LIMIT 1
    ) AS random_date


    1. pg_expecto Автор
      03.12.2025 06:00

      Какие подробности вас интересуют ?

      Задача очень простая - получить случайную дату из периода.


      1. baldr
        03.12.2025 06:00

        Вы понимаете, что вы эти сортировки включаете в измерения, хотя, по факту, этот параметр должен приходить от пользователя? Ваши запросы и измерения некорректны - я вам на это указывал несколько раз.

        Ну и вообще вся "сложная схема" базы данных у вас - это 9 табличек и 12 Gb? У меня есть таблицы в два раза больше этих 12Gb и это даже не big data. 12 Gb влезет даже в RAM на небольшом сервере. Перестаньте заниматься ерундой.


  1. baldr
    03.12.2025 06:00

    @moderator, а можно как-то ограничить поток автогенерированного псевдотехнического бреда от этого пользователя? Он засоряет всем ленту, отвлекает от нормальных статей..

    Все предыдущие статьи заминусованы, на замечания автор не реагирует. Реального смысла во всех этих "измерениях" нет.