About |

Быстрый random() select

Я долго думал как без костылей, а только на уровне базы (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 раз! И чем больше база тем больше будет разница в этих значениях. 

Очень жаль, что в order by random() работает так невыносимо медленно.

Поддержка таблицы с непрерывным интервалом в актуальном состоянии. 

У меня было 2 мысли как обновлять таблицу непрерывных значений в случае удаления записей (т.к. если ее не обновлять появятся “дырки” на интервале). Первый варинт — триггеры, безусловно самый быстрый. Но я от него отказался в пользу переопределения методов save()/delete() у джанговской модели — т.к. в данном случае проще контролироть процесс в случае ошибки, а большая скорость на delete()/save() мне не нужна. 

С методом save() все просто — добавляем новое значение в интервал (max()+1). Для метода delete() — берем максимальное значение интервала и обновляем эту запись -> затыкаем им дырку образовавщуюся в результате delete(). Вот собственно и все.

Add post to: Delicious Reddit Slashdot Digg Technorati Google
(already: 1) Comment post

Comments

No comments for this post

Comment form for «Быстрый random() select»

Required. 30 chars of fewer.

Required.

captcha image Please, enter symbols, which you see on the image