当您可以按某些唯一索引对表进行排序时,我知道解决方案
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 行。(一个小的
OFFSET
和LIMIT
可能会导致这种情况):在INDEX(sorter, id)
的 BTree 内部跳过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,我刚才描述的内容是在INDEX(id)
的BTree中完成的。 ;但它仍然必须在 Btree 和 .MYD
之间来回跳动数据所在的文件。 (这是 InnoDB 的设计本质上比 MyISAM 的设计更高效的示例。)
如果您的目标是从表中获取一堆随机行,请阅读我的 treatise 。总之,有更快的方法,但没有一个是“完美的”,尽管通常“足够好”。
关于mysql分页从按随机数据索引排序的大表中选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52745039/