mysql从大表中按随机数据索引对页面进行分页选择

时间:2018-10-10 16:39:59

标签: mysql indexing

我知道解决方案,当您可以按某些唯一索引对表格进行排序

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顺序中按排序器限制选择ID 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分钟以上,并且仅用于收集ID

1 个答案:

答案 0 :(得分:0)

使用OFFSET效率不如您想象的那样。使用LIMIT 1512123, 10000,必须跨过1512123行。该数字越大,查询运行越慢。

解释EXPLAINs ...

中的区别

“解决方案1”使用SELECT *;您没有覆盖指数。因此,可以通过两种方式运行查询:

  • (这样做):扫描表中的“ ALL”,收集所有列(*);分类;跳过512123行;并投放10000或1000行。

  • (一个小的OFFSETLIMIT可能会导致此问题):在INDEX(sorter, id)的BTree内部,跳过OFFSET行;抢LIMIT行;对于索引中的每个抓取行,请使用字节偏移量到达数据文件(注意:您使用的是MyISAM,而不是InnoDB)来查找行;抢*并将其交付。不需要排序。

不幸的是,优化器没有足够的统计信息,也没有足够的智慧来始终正确地在这两个选择之间进行选择。

“解决方案2”使用“覆盖”索引INDEX(sorter, id)。 (线索:“使用索引”。)其中包含在查询(sorter)任意位置找到的 all 列(仅idselect id from t order by sorter limit 1512123, 10000;),因此可以(通常通常)优先使用索引而不是扫描表。

另一个涉及where id>=$i的解决方案。这样可以避免OFFSET。但是,由于使用的是MyISAM,因此无法将索引和数据一起“聚类”。使用InnoDB,数据根据PRIMARY KEY进行排序。如果那是id,则可以通过直接跳入数据中间(在$i处)开始查询。使用MyISAM,我刚刚描述的内容是在INDEX(id)的BTree中完成的;但是它仍然必须在该Btree和数据所在的.MYD文件之间来回跳动。 (这是InnoDB的设计从本质上比MyISAM的效率更高的示例。)

如果您的目标是从表中获得一堆随机行,请阅读我的treatise。总而言之,有更快的方法,但通常都不是“完美的”,尽管通常“足够好”。