PostgreSQL : Seq Scan против индексов - парадоксальный, на первый взгляд, итог нагрузочного тестирования
Взято с основного технического канала Postgres DBA (возможны правки в исходной статье).
Cчитается, что индексы — это панацея для производительности СУБД, своего рода «волшебная таблетка» для ускорения запросов. Но что, если под давлением конкуренции и стремительно растущей нагрузки классические подходы дают сбой?
В этой статье показаны результаты нагрузочного тестирования PostgreSQL в условиях нагрузочного тестирования, которые привели к парадоксальному на первый взгляд выводу. Когда количество одновременных операций растет, а данные активно читаются, дорогостоящие индексы могут не справиться, превратившись из помощников во вредителей. И как оказалось, старый добрый Seq Scan — метод полного сканирования таблицы — неожиданно стал в итоге самым эффективным решением в этом сценарии.
Постановка экспериментов
Тестовая таблица
CREATE TABLE pgbench_test
(
aid integer PRIMARY KEY ,
bid integer,
abalance integer,
filler character(84)
);
ALTER TABLE pgbench_test ADD CONSTRAINT "pgbench_test_bid_fkey" FOREIGN KEY (bid) REFERENCES pgbench_branches(bid);
INSERT INTO pgbench_test ( aid , bid , abalance , filler )
SELECT
id ,
floor(random() * 685 ) + 1 ,
floor(random() * (68500000 - 1 + 1)) + 1 ,
md5(random()::text)
FROM generate_series(1,1000000) id;
Тестовый запрос
select test.abalance
from pgbench_accounts acc
join pgbench_test test on (test.bid = acc.bid )
where acc.aid = current_aid ;
Эксперимент-1 : Метод доступа Seq Scan
План выполнения тестового запроса
Стоимость плана = 21620.62
Gather (cost=1002.80..21620.62 rows=1460 width=4) (actual time=4.088..329.017 rows=1468 loops=1)
Workers Planned: 3
Workers Launched: 3
-> Hash Join (cost=2.80..20474.62 rows=471 width=4) (actual time=1.821..300.589 rows=367 loops=4)
-> Parallel Seq Scan on pgbench_test test (cost=0.00..19619.81 rows=322581 width=8) (actual time=0.692..263.390 rows=250000 loops=4)
-> Hash (cost=2.79..2.79 rows=1 width=4) (actual time=0.488..0.489 rows=1 loops=4)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Index Scan using pgbench_accounts_pkey on pgbench_accounts acc (cost=0.57..2.79 rows=1 width=4) (actual time=0.472..0.474 rows=1 loops=4)
Index Cond: (aid = 51440641)
Planning Time: 1.977 ms
Execution Time: 329.301 ms
Эксперимент-2 : Метод доступа Bitmap Index Scan
CREATE INDEX pgbench_test_idx ON pgbench_test ( bid );
План выполнения тестового запроса
Стоимость плана = 1546.55
Nested Loop (cost=14.51..1546.55 rows=1460 width=4) (actual time=0.894..366.050 rows=1468 loops=1)
-> Index Scan using pgbench_accounts_pkey on pgbench_accounts acc (cost=0.57..2.79 rows=1 width=4) (actual time=0.217..0.227 rows=1 loops=1)
Index Cond: (aid = 51440641)
-> Bitmap Heap Scan on pgbench_test test (cost=13.94..1529.17 rows=1460 width=8) (actual time=0.669..365.312 rows=1468 loops=1)
Recheck Cond: (bid = acc.bid)
Heap Blocks: exact=1407
-> Bitmap Index Scan on pgbench_test_idx (cost=0.00..13.57 rows=1460 width=0) (actual time=0.355..0.356 rows=1468 loops=1)
Index Cond: (bid = acc.bid)
Planning Time: 2.634 ms
Execution Time: 366.419 ms
Эксперимент-3 : Метод доступа Index Only Scan
CREATE INDEX idx_pgbench_test_bid_abalance ON pgbench_test(bid) INCLUDE (abalance);
План выполнения тестового запроса
Стоимость плана = 48.86
Nested Loop (cost=0.99..48.86 rows=1460 width=4) (actual time=0.771..1.186 rows=1432 loops=1)
-> Index Scan using pgbench_accounts_pkey on pgbench_accounts acc (cost=0.57..2.79 rows=1 width=4) (actual time=0.736..0.737 rows=1 loops=1)
Index Cond: (aid = 51440641)
-> Index Only Scan using idx_pgbench_test_bid_abalance on pgbench_test test (cost=0.42..31.47 rows=1460 width=8) (actual time=0.029..0.289 rows=1432 loops=1)
Index Cond: (bid = acc.bid)
Heap Fetches: 0
Planning Time: 4.949 ms
Execution Time: 1.302 ms
Нагрузка на СУБД
Результаты экспериментов:
До 8-ми соединений - наибольшая производительность получена с использованием метода доступа Bitmap Index Scan.
После 8-ми соединений и до 22-х - наибольшая производительность получена с использованием метода доступа Seq Scan.
Причины эффективности Seq Scan при параллельной нагрузке
1. Увеличение нагрузки на подсистему ввода-вывода
Без индекса: Sequential Scan читает данные большими последовательными блоками
С индексом: Index Scans создают случайный доступ к диску
2. Конкуренция за буферный кэш
Индекс занимает место в shared_buffers
Вытесняет полезные данные из кэша
Каждое соединение читает разные части индекса → больше промахов кэш
3. Блокировки в системных каталогах
При использовании индекса PostgreSQL обращается к системным каталогам
Увеличивается конкуренция за pg_index, pg_class
Особенно заметно при многих одновременных соединениях
4. CPU overhead
Обработка индекса требует больше CPU операций
Bitmap Index Scan + Bitmap Heap Scan сложнее чем простой Seq Scan
При росте соединений CPU становится узким местом































