mysql постраничный выбор из большой таблицы, отсортированный по индексу случайных данных - PullRequest
0 голосов
/ 10 октября 2018

Я знаю решение, когда вы можете отсортировать таблицу по некоторому уникальному индексу

SELECT user_id, external_id, name, metadata, date_created
FROM users
WHERE user_id > 51234123 
ORDER BY user_id ASC
LIMIT 10000;

, но в моем случае я хочу отсортировать таблицу по некоторому индексу, который имеет случайные данные

CREATE TABLE `t` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `sorter` bigint(20) NOT NULL,
  `data1` varchar(200) NOT NULL,
  `data2` varchar(200) NOT NULL,
  `data3` varchar(200) NOT NULL,
  `data4` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `sorter` (`sorter`),
  KEY `id` (`id`,`sorter`),
  KEY `sorter_2` (`sorter`,`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

for ($i = 0; $i < 2e6; $i++)
    $db->query("INSERT INTO `t` (`sorter`, `data1`, `data2`, `data3`, `data4`) VALUES (rand()*3e17, rand(), rand(), rand(), rand())");

for ($i = 0; $i < 1e6; $i++)
    $db->query("INSERT INTO `t` (`sorter`, `data1`, `data2`, `data3`, `data4`) VALUES (0, rand(), rand(), rand(), rand())");

решение 1:

for ($i = 0; $i < $maxId; $i += $step)

    select * from t
    where id>=$i
    order by sorter
    limit $step

select * from t order by sorter limit 512123, 10000;
10000 rows in set (9.22 sec)

select * from t order by sorter limit 512123, 1000;
1000 rows in set (6.25 sec)

+------+-------------+-------+------+---------------+------+---------+------+---------+----------------+
| id   | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra          |
+------+-------------+-------+------+---------------+------+---------+------+---------+----------------+
|    1 | SIMPLE      | t     | ALL  | NULL          | NULL | NULL    | NULL | 3000000 | Using filesort |
+------+-------------+-------+------+---------------+------+---------+------+---------+----------------+

решение 2:

выбор идентификатора из t порядка по пределу сортировщика 1512123, 10000;

+------+-------------+-------+-------+---------------+----------+---------+------+---------+-------------+
| id   | select_type | table | type  | possible_keys | key      | key_len | ref  | rows    | Extra       |
+------+-------------+-------+-------+---------------+----------+---------+------+---------+-------------+
|    1 | SIMPLE      | t     | index | NULL          | sorter_2 | 16      | NULL | 1522123 | Using index |
+------+-------------+-------+-------+---------------+----------+---------+------+---------+-------------+

10000 строк в наборе (0,74 с))

0,74 звучит хорошо, но для всех столов это занимает 0,74 * 3000e3 / 10e3 / 60 = более 3 минут, и это только для сбора идентификаторов

1 Ответ

0 голосов
/ 11 октября 2018

Использование OFFSET не так эффективно, как вы думаете.При LIMIT 1512123, 10000 1512123 строки должны быть перешагнуты.Чем больше это число, тем медленнее выполняется запрос.

Чтобы объяснить разницу в EXPLAINs ...

«Решение 1» использует SELECT *;у вас нет индекса покрытия для этого.Итак, есть два способа выполнения запроса:

  • (он это сделал): отсканировать «ВСЕ» таблицу, собрав все столбцы (*);Сортировать;пропустить 512123 строки;и доставьте 10000 или 1000 строк.

  • (небольшие OFFSET и LIMIT могут привести к этому): Внутри BTree для INDEX(sorter, id) пропустите OFFSET строки;возьмите LIMIT строки;для каждой захваченной строки в индексе перейдите в файл данных, используя смещение в байтах (примечание: вы используете MyISAM, а не InnoDB), чтобы найти строку;захватить * и доставить его.Сортировка не требуется.

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

«Решение 2» используетиндекс "покрытия" INDEX(sorter, id).(Подсказка: «Использование индекса».) Он содержит все столбцы (только sorter и id), найденные в любом месте запроса (select id from t order by sorter limit 1512123, 10000;), поэтому индекс может (и обычно будет) предпочтительнее, чем сканирование таблицы.

В другом решении упоминается where id>=$i.Это позволяет избежать OFFSET.Однако, поскольку вы используете MyISAM, индекс и данные не могут быть «кластеризованы» вместе.С InnoDB данные упорядочены в соответствии с PRIMARY KEY.Если это id, то запрос может начаться с перехода прямо в середину данных (в $i).С MyISAM то, что я только что описал, выполняется в BTree для INDEX(id);но он все еще должен прыгать туда-сюда между этим Btree и файлом .MYD, в котором находятся данные.(Это пример того, как дизайн InnoDB по своей природе более эффективен, чем дизайн MyISAM.)

Если ваша цель - получить несколько случайных строк из таблицы, прочитайте мой трактат .Таким образом, есть более быстрые способы, но ни один не является «идеальным», хотя обычно «достаточно хорошим».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...