Я долго думал как без костылей, а только на уровне базы (PostgreSQL) сделать быстрый select * from table order by random(); К сожалению ничего хорошего найти не получилось. Вот explain на базе из около 300 000 записей:
Limit (cost=29127.46..29127.71 rows=100 width=358) (actual time=2531.542..2531.900 rows=100 loops=1)
-> Sort (cost=29127.46..29850.98 rows=289406 width=358) (actual time=2531.536..2531.658 rows=100 loops=1)
Sort Key: (random())
Sort Method: top-N heapsort Memory: 67kB
-> Seq Scan on wall_wallpapper (cost=0.00..18066.58 rows=289406 width=358) (actual time=0.033..1139.509 rows=276459 loops=1)
Total runtime: 2532.240 ms
Не очень быстро. Очевидным решением было генерировать массив случайных значений на уровне кода и выбирать жанные по ключу. Чтобы выбирать по сгенерированным в коде значениям нам нужен непрерывный интервал значений (чтобы не было “дырок”). Это решение мне не очень нравится, но работает оно очень быстро. Итак для этого я создал таблицу с непрерывным интервалом значений, и ключом на исходную таблицу (в моем случае таблицу с обоями) -
Column | Type | Modifiers | Storage | Description
–––+–––+––––––––––––––––––––+–––+––––-
id | integer | not null default nextval(‘wall_wallhash_id_seq’::regclass) | plain |
h_id | integer | not null | plain |
w_id_id | integer | not null | plain |
Indexes:
“wall_wallhash_pkey” PRIMARY KEY, btree (id)
“wall_wallhash_h_id_key” UNIQUE, btree (h_id)
“wall_wallhash_w_id_id” btree (w_id_id)
Foreign-key constraints:
“wall_wallhash_w_id_id_fkey” FOREIGN KEY (w_id_id) REFERENCES wall_wallpapper(id) DEFERRABLE INITIALLY DEFERRED
Has OIDs: no
где h_id — значение на непрерывном интервале, w_id_id — ключ на таблицу с обоями (джанга сгенерировала такое название, т.к. я сначала хотел делать триггером и назвать ключ w_id)
Эту таблицу я заполнил непрерывным интервалом с ссылками на таблицу обоев -
CREATE SEQUENCE tmp_hash;
insert into wall_wallhash (h_id, w_id_id) (SELECT nextval(‘tmp_hash’), id from wall_wallpapper);
В коде я генерирую случайные значения в интервале от 0 до max() значения таблицы ключей. И выбираю данные примерно следующим запросом -
select * from wall_wallpapper where active=TRUE and id in (select w_id_id from wall_wallhash where h_id in (23260,79052,115503,235463,22031,17547,91859,95542,17186,244111,136111, …. 101076,218414,227782,120009,25861))
Nested Loop (cost=722.88..1526.57 rows=100 width=358) (actual time=3.687..6.449 rows=100 loops=1)
-> HashAggregate (cost=722.88..723.88 rows=100 width=4) (actual time=3.605..3.740 rows=100 loops=1)
-> Bitmap Heap Scan on wall_wallhash (cost=397.92..722.63 rows=100 width=4) (actual time=2.180..3.390 rows=100 loops=1)
Recheck Cond: (h_id = ANY (‘{23260,79052,115503,235463,22031,17547,……..101076,218414,227782,120009,25861}’::integer[]))
-> Bitmap Index Scan on wall_wallhash_h_id_key (cost=0.00..397.90 rows=100 width=0) (actual time=2.140..2.140 rows=100 loops=1)
Index Cond: (h_id = ANY (‘{23260,79052,115503,235463,….. 227782,120009,25861}’::integer[]))
-> Index Scan using wall_wallpapper_pkey on wall_wallpapper (cost=0.00..8.01 rows=1 width=358) (actual time=0.021..0.023 rows=1 loops=100)
Index Cond: (wall_wallpapper.id = wall_wallhash.w_id_id)
Filter: (wall_wallpapper.active)
Total runtime: 6.737 ms
Как видно время уменьшилось почти в 400 раз! И чем больше база тем больше будет разница в этих значениях.
continue reading