LINUX.ORG.RU

Оптимизация таблицы содержащей около 2 миллионов записей


1

3

Имеется таблица содержащая около 2 миллионов записей. Выборка из начала (LIMIT 0, 30) происходит довольно быстро, однако чем больше OFFSET тем медленнее выборка. Выборка LIMIT 1782420, 30 не завершилась даже за 5 минут. Даже несколько минут это не дело т.к. может быть большое количество пользователей работающих с веб приложением одновременно и запрашивающих страницы с большими номерами. Никакие индексы не помогают (снижают время доступа к первым страницам но не к последним). Fixed (ROW_FORMAT) не помог. Как это можно оптимизировать? Неужели современная вычислительная техника не способна на создание приложения просто показывающего пользователям такой объём информации?

★

Большой offset зло. Сервак считывает ВЕСЬ оффсет, потом выплевывает limit.

Лучше использовать больше-меньше на индекс. К примеру, если у тебя это посты, то у них есть ид. Соответственно запрос не

select * from posts limit 30 offset 100500
а
select * from posts where post_id >= 100500 limit 30

Но это как вариант, каждый случай надо отдельно рассматривать.

anonymous
()
Ответ на: комментарий от anonymous

Ага, а потом у тебя записи из области до post_id > 100500 удаляются и постраничный вывод превращается в гуано..

To TS: никогда не поверю, что выборка таблицы с хотя бы одним индексированным полем (которое к тому же primary key) на таком небольшом объеме данных может длиться так долго

Photon79
()

Провёл эксперимент: таблица на 1 миллион записей. Первый запрос с limit 950000,30 выполнился за 4.5 секунды. Все последующие с любым limit ~0.5 секунды

Photon79
()
Ответ на: комментарий от Photon79
CREATE TABLE `videos` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `site` varchar(255) NOT NULL,
  `site_id` bigint(20) unsigned NOT NULL,
  `site_url` varchar(255) NOT NULL,
  `title` text NOT NULL,
  `thumbnails` text NOT NULL,
  `duration_txt` varchar(32) NOT NULL,
  `duration` bigint(20) unsigned NOT NULL,
  `embed` text NOT NULL,
  `created_on` datetime NOT NULL,
  `status` enum('active','deleted') NOT NULL DEFAULT 'active',
  `views` bigint(20) unsigned NOT NULL DEFAULT '0',
  `rating` bigint(20) unsigned NOT NULL DEFAULT '0',
  `voted` bigint(20) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `site` (`site`),
  KEY `site_id` (`site_id`),
  KEY `site_url` (`site_url`),
  KEY `status` (`status`),
  KEY `created_on_id` (`created_on`,`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1797645 DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED

Запрос `SELECT * FROM f_videos LIMIT 1782420, 30;` длится бесконечно долго. Что я делаю не так?

tyler19 ★
() автор топика
Ответ на: комментарий от tyler19

Что я делаю не так?

ENGINE=MyISAM

И Вы еще спрашиваешь?

К слову в постгресе, у меня по два мульена записей в сутки, ивсе это за год вполне себе выдается, даже на особокучерявых запросах. База уже больше 200гиг.

Deleted
()
Ответ на: комментарий от Photon79

Ага, а потом у тебя записи из области до post_id > 100500 удаляются

Не удаляй, помечай как удаленные

anonymous
()
Ответ на: комментарий от tyler19

MyISAM не стоит использовать. Плюс надо, как уже писали выше, избавляться от LIMIT. Ну и на конфиг дай посмотреть.

Anoxemian ★★★★★
()
Ответ на: комментарий от tyler19

Вчитался, из всех индексов только 2 приемлемы - created_on_id и site_id. Архитектор наркоман, однозначно.

Anoxemian ★★★★★
()
Ответ на: комментарий от Anoxemian

Что не так с полема status? По поводу индексов я с ними экспериментировал поэтому много лишних. Скажите какие оставить. От лишних индексов запрос тормозится разве?

tyler19 ★
() автор топика
Ответ на: комментарий от Deleted

ENGINE=MyISAM

И Вы еще спрашиваешь?

Насколько я понимаю InnoDB ещё тормознее но поддерживает транзакции.

tyler19 ★
() автор топика

Покажи show create table и explain на проблемный select. Есть три варианта: 1. В условии выборки (where) участвует поле, которого нет в индексе, поэтому большой offset по факту превращается в seq scan 2. Индекс банально не влезает в память 3. mysql - кривое глючное говно, а не база данных

anonymous
()
Ответ на: комментарий от tyler19

судя по тому что я увидел, key_buffer уводит систему в жестокий своп. Я намекну - как думаешь, насколько операция varchar(255)==varchar(255) затратнее для процессора, чем операция int(11)==int(11) ? а уж enum(varchar,varchar) вместо enum(int,int), вообще не от мира сего. и даже не enum должен быть, а тупо int. Enum так или иначе тянет за собой дополнительную логику - он вообще не нужен. Ну и конфиг-то покажи плиз.

Anoxemian ★★★★★
()
Ответ на: комментарий от tyler19

Что я делаю не так?

SELECT * FROM f_videos ORDER BY id LIMIT 1782420, 30

anonymous
()
Ответ на: комментарий от anonymous

Покажи show create table

Оптимизация таблицы содержащей около 2 миллионов записей (комментарий)

mysql> EXPLAIN SELECT * FROM f_videos LIMIT 1782420, 30;
+----+-------------+----------+------+---------------+------+---------+------+---------+-------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows    | Extra |
+----+-------------+----------+------+---------------+------+---------+------+---------+-------+
|  1 | SIMPLE      | f_videos | ALL  | NULL          | NULL | NULL    | NULL | 1782614 |       |
+----+-------------+----------+------+---------------+------+---------+------+---------+-------+
1 row in set (0.03 sec)

mysql> 

3. mysql - кривое глючное говно, а не база данных

В настоящее время занимаюсь конвертацией в PostgreSQL, о результатах сообщу.

tyler19 ★
() автор топика
Ответ на: комментарий от Deleted

Ну что тут можно сказать?

В твоем случае действительно лучше бы промолчать.

anonymous
()
Ответ на: комментарий от Anoxemian

судя по тому что я увидел, key_buffer уводит систему в жестокий своп. Я намекну - как думаешь, насколько операция varchar(255)==varchar(255) затратнее для процессора, чем операция int(11)==int(11) ? а уж enum(varchar,varchar) вместо enum(int,int), вообще не от мира сего. и даже не enum должен быть, а тупо int. Enum так или иначе тянет за собой дополнительную логику - он вообще не нужен. Ну и конфиг-то покажи плиз.

Думал это предусмотрено и строки конвертируются в INT автоматически как в C. А так придётся запоминать какое число что значит.

[mysqld]
character-set-server        = utf8
user                        = mysql
port                        = 3306
socket                      = /var/run/mysqld/mysqld.sock
pid-file                    = /var/run/mysqld/mysqld.pid
log-error                   = /var/log/mysql/mysqld.err
basedir                     = /usr
datadir                     = /var/lib/mysql
skip-external-locking
key_buffer                  = 16M
max_allowed_packet          = 100M
table_cache                 = 64
sort_buffer_size            = 512K
net_buffer_length           = 8K
read_buffer_size            = 256K
read_rnd_buffer_size        = 512K
myisam_sort_buffer_size     = 8M
language                    = /usr/share/mysql/english
tyler19 ★
() автор топика
Ответ на: комментарий от tyler19

EXPLAIN SELECT * FROM f_videos LIMIT 1782420, 30

ORDER BY однозначно не хватает. В постгри будет точно так же.

anonymous
()
Ответ на: комментарий от tyler19

не надо ничего запоминать. Просто надо все, абсолютно все, кроме числовых примитивов выносить в соседние таблицы, а при выборке делать left outer join, и не тупить с индексами. В этом как бы и смысл реляционных баз. кроме того, это сократит физический размер базы конкретно у тебя раза в два.

Anoxemian ★★★★★
()
Ответ на: комментарий от Anoxemian

надо все, абсолютно все, кроме числовых примитивов выносить в соседние таблицы, а при выборке делать left outer join

Чтоб тебе в аду джойны руками делать. Нормализация нигде кроме учебников не живет. join = seek. О том, сколько seek'ов в секунду может сделать жесткий диск, спроси у гугла.

anonymous
()
Ответ на: комментарий от Anoxemian

Ну да, давай мерятся длинной опыта 8) Ты еслиб не говорил _любой_ то у тебяб не сокращался опыт.

Deleted
()
Ответ на: комментарий от Deleted

Убогий мускуль не в силах скешировать табличку в полсотни записей?

Чего ты собрался кешировать при связи 1 к 1, убогий?

anonymous
()
Ответ на: комментарий от Deleted

Ну 2кк записей явно больше, чем какой бы то ни было вынесенный справочник, соответственно нечего тут оптимизировать.

Anoxemian ★★★★★
()
Ответ на: комментарий от anonymous

У тебя траблы с пониманием текста.

Deleted
()
Ответ на: комментарий от Anoxemian

Ну ты веришь, что движек базы настолько умен? На практике у меня оракел показывал в аналогичных случаях проседание (тупая оптимизация запроса вложенным селектом сильно улучшала план), прозреваю, что мускуль будет стольже фееричен.

Deleted
()
Ответ на: комментарий от Anoxemian

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

anonymous
()
Ответ на: комментарий от Deleted

Ммм, хорошо сказано. Из того что я видел, вложенный селект - это почти всегда ухудшение производительности. Конкретно с мускулем вообще беда, он в статике (вьюхи) до сих пор вложенные селекты не понимает. Но вот с индексами работает именно так «умно».

Anoxemian ★★★★★
()
Ответ на: комментарий от Anoxemian

Уточню.

select a.*, b.name
  from atable a
  join btable b on a.bid=b.id

-- оптимизируется в 
select a.*, 
       (select b.name from btable b where a.bid=b.id) as name
  from atable a
  

Очевидно если atable огромен, а btable - наносправочник

Deleted
()
Ответ на: комментарий от Deleted

Если atable сильно больше btable, то такая оптимизация приведет только к потере эффективности. Это проход ALL по всему atable плюс количество запросов к btable == ALL, итого количество селектов овер 9000. Такая оптимизация видится немного не логичной, не?

Anoxemian ★★★★★
()
Ответ на: комментарий от Anoxemian

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

Deleted
()
Ответ на: комментарий от tyler19

Вот эти поля я бы вынес в отдельную таблицу -

  `title` text NOT NULL,
  `thumbnails` text NOT NULL,
  `embed` text NOT NULL,

Особенно если там большой объем данных.

pi11 ★★★★★
()
Ответ на: комментарий от Anoxemian

key_buffer=256M net_buffer_length=64k read_buffer_size=1M read_rnd_buffer_size=1M

Помоло, завершается за ~10 секунд выборка.

ну и на innodb-таки переходи.

Сейчас этим занимаюсь, запустил alter table videos engine=innodb;

tyler19 ★
() автор топика
Ответ на: комментарий от Deleted

К слову в постгресе, у меня по два мульена записей в сутки, ивсе это за год вполне себе выдается, даже на особокучерявых запросах. База уже больше 200гиг.

Сконвертировал наконец то базу в PostgreSQL. И вот что получается:

b32=# \di
                     List of relations
 Schema |         Name         | Type  |  Owner   | Table  
--------+----------------------+-------+----------+--------
 public | videos_created_on    | index | postgres | videos
 public | videos_created_on_id | index | postgres | videos
 public | videos_pkey          | index | postgres | videos
(3 rows)

b32=# explain select * from videos order by id OFFSET 1505000 LIMIT 30;
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Limit  (cost=144603.26..144606.14 rows=30 width=509)
   ->  Index Scan using videos_pkey on videos  (cost=0.00..171276.94 rows=1782614 width=509)
(2 rows)

b32=# explain select * from videos order by created_on OFFSET 1505000 LIMIT 30;
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Limit  (cost=1964544.56..1964544.63 rows=30 width=509)
   ->  Sort  (cost=1960782.06..1965238.59 rows=1782614 width=509)
         Sort Key: created_on
         ->  Seq Scan on videos  (cost=0.00..142793.14 rows=1782614 width=509)
(4 rows)

b32=# explain select * from videos order by created_on, id OFFSET 1505000 LIMIT 30;
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Limit  (cost=1964544.56..1964544.63 rows=30 width=509)
   ->  Sort  (cost=1960782.06..1965238.59 rows=1782614 width=509)
         Sort Key: created_on, id
         ->  Seq Scan on videos  (cost=0.00..142793.14 rows=1782614 width=509)
(4 rows)

b32=# 

ORDER BY id с 'Index scan' выполняется довольно быстро. Однако ORDER BY created_on а также ORDER BY created_on DESC, id DESC (что собственно мне в идеале хотелось бы зависают надолго т.е. не дождался и зделал Ctrl+C). Несмотря на то что добавил индексы как для created_on так и для (created_on, id). В чём дело?

tyler19 ★
() автор топика
Ответ на: комментарий от Deleted

Так что делать? Почему не происходит то что описано здесь http://www.postgresql.org/docs/9.2/static/indexes-ordering.html и как оптимизировать? Я уже готов плюнуть на всё и написать свой простенький велосипед для данного частного случая.

tyler19 ★
() автор топика
Ответ на: комментарий от tyler19

возможно индексы чем то не по нраву планировщику надо смотреть как ты их создаешь и что за типы в таблице, вообще планировщик игнорирует индексы - когда запрашивается «все», некоторые субд также игнорируют индексы когда в select вместо перечисления полей используется * - но не помню грешит ли этим постгрес

Deleted
()
Ответ на: комментарий от Deleted

SELECT id даёт такой же результат.

create index videos_created_on on videos (created_on);
create index videos_created_on_id on videos (created_on,id);

Конкретно какие шаги можно предпринять? С чего начать?

tyler19 ★
() автор топика
Ответ на: комментарий от tyler19

А покажи план выполнения

explain
  select * 
    from videos v
   where v.id>xxxx /*тут айдишник какой нить 1505000 записи после которой можно 30 отсчитать*/ 
   order by created_on, id LIMIT 30;
Deleted
()
Ответ на: комментарий от Anoxemian

Если atable сильно больше btable, то такая оптимизация приведет только к потере эффективности. Это проход ALL по всему atable плюс количество запросов к btable == ALL, итого количество селектов овер 9000. Такая оптимизация видится немного не логичной, не?

Вполне логичная оптимизация, так как обычно используются всякие where, и вместо прохода по всему atable проходится только по нужным строкам.

andrewzvn ★
()
Ответ на: комментарий от tyler19

Если ты будешь сортировать по created_on и без where - то работать будет в любом случае долго.
p.s. текстовые поля (которые text) вынеси в отдельную табицу - на больших таблицах дает хороший прирост производительности.

pi11 ★★★★★
()
Вы не можете добавлять комментарии в эту тему: топик перемещен в архив.