About |

Archive for May, 2011

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

 continue reading