mysql分页从按随机数据索引排序的大表中选择

标签 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 order 中选择 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 分钟,而且仅用于收集 ids

最佳答案

使用OFFSET并不像你想象的那么高效。与LIMIT 1512123, 10000 ,必须跨过 1512123 行。该数字越大,查询运行速度越慢。

解释 EXPLAINs 中的差异...

“解决方案 1”使用 SELECT * ;你没有它的覆盖索引。因此,有两种方式可以运行查询:

  • (它这样做了):扫描“全部”表,收集所有列( * );种类;跳过 512123 行;并交付 10000 或 1000 行。

  • (一个小的 OFFSETLIMIT 可能会导致这种情况):在 INDEX(sorter, id) 的 BTree 内部跳过 OFFSET行;捕获LIMIT行;对于索引中每个抓取的行,使用字节偏移量进入数据文件(注意:您使用的是 MyISAM,而不是 InnoDB)来查找该行;抢*并交付它。无需排序。

不幸的是,优化器没有足够的统计数据,也没有足够的智能来始终在这两个选择之间做出正确的选择。

“解决方案 2”使用“覆盖”索引 INDEX(sorter, id) 。 (线索:“使用索引”。)这包含在查询( sorter )中任何位置找到的所有列(仅 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 。总之,有更快的方法,但没有一个是“完美的”,尽管通常“足够好”。

关于mysql分页从按随机数据索引排序的大表中选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52745039/

相关文章:

mysql - 在 CakePhP 3.0 中,在主键以外的唯一索引上搜索 MySQL 数据库的首选方法是什么?

php - MySql 可以插入值

php - 为什么我必须使用 PDO 启动事务,然后提交它才能从 MySQL DB 中删除数据

mysql - 使用php打印mysql数据

javascript - 如果元素的索引超过 3,选择元素的最佳方法是什么?

jquery - 获取 ul 中 li 的索引

performance - MongoDB gridFS - 文件名长度、索引、性能

solr - 维护 Sunspot Solr 索引的正确方法是什么?

php - MySQL的性能

image - 查找矩阵中元素的索引