众所周知,在MySQL中,如果直接 ORDER BY RAND() 的话,效率非常差,因为会多次执行。事实上,如果等值查询也是用 RAND() 的话也如此,我们先来看看下面这几个SQL的不同执行计划和执行耗时。首先,看下建表DDL,这是一个没有显式自增主键的InnoDB表:
[yejr@imysql]> show create table t_innodb_random/G*************************** 1. row ***************************Table: t_innodb_randomCreate Table: CREATE TABLE `t_innodb_random` (`id` int(10) unsigned NOT NULL,`user` varchar(64) NOT NULL DEFAULT '',KEY `idx_id` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=latin1
往这个表里灌入一些测试数据,至少10万以上, id 字段也是乱序的。
[yejr@imysql]> select count(*) from t_innodb_random/G*************************** 1. row ***************************count(*): 393216
1、常量等值检索:
[yejr@imysql]> explain select id from t_innodb_random where id = 13412/G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: t_innodb_randomtype: refpossible_keys: idx_idkey: idx_idkey_len: 4<strong>ref: constrows: 1Extra: Using index</strong>[yejr@imysql]> select id from t_innodb_random where id = 13412;1 row in set (0.00 sec)
可以看到执行计划很不错,是常量等值查询,速度非常快。
2、使用RAND()函数乘以常量,求得随机数后检索:
[yejr@imysql]> explain select id from t_innodb_random where id = round(rand()*13241324)/G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: t_innodb_randomtype: indexpossible_keys: NULLkey: idx_idkey_len: 4<strong>ref: NULLrows: 393345Extra: Using where; Using index</strong>[yejr@imysql]> select id from t_innodb_random where id = round(rand()*13241324)/GEmpty set (0.26 sec)
可以看到执行计划很糟糕,虽然是只扫描索引,但是做了全索引扫描,效率非常差。因为WHERE条件中包含了RAND(),使得MySQL把它当做变量来处理,无法用常量等值的方式查询,效率很低。
我们把常量改成取t_innodb_random表的最大id值,再乘以RAND()求得随机数后检索看看什么情况:
[yejr@imysql]> explain select id from t_innodb_random where id = round(rand()*(select max(id) from t_innodb_random))/G*************************** 1. row ***************************id: 1select_type: PRIMARYtable: t_innodb_randomtype: indexpossible_keys: NULLkey: idx_idkey_len: 4<strong>ref: NULLrows: 393345Extra: Using where; Using index</strong>*************************** 2. row ***************************id: 2select_type: SUBQUERYtable: NULLtype: NULLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: NULLExtra: Select tables optimized away[yejr@imysql]> select id from t_innodb_random where id = round(rand()*(select max(id) from t_innodb_random))/GEmpty set (0.27 sec)
可以看到,执行计划依然是全索引扫描,执行耗时也基本相当。
3、改造成普通子查询模式 ,这里有两次子查询
<strong>[yejr@imysql]> explain select id from t_innodb_random where id = (select round(rand()*(select max(id) from t_innodb_random)) as nid)/G*************************** 1. row ***************************id: 1select_type: PRIMARYtable: t_innodb_randomtype: indexpossible_keys: NULLkey: idx_idkey_len: 4ref: NULLrows: 393345Extra: Using where; Using index*************************** 2. row ***************************id: 3select_type: SUBQUERYtable: NULLtype: NULLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: NULLExtra: Select tables optimized away[yejr@imysql]> select id from t_innodb_random where id = (select round(rand()*(select max(id) from t_innodb_random)) as nid)/GEmpty set (0.27 sec)</strong>
可以看到,执行计划也不好,执行耗时较慢。
4、改造成JOIN关联查询,不过最大值还是用常量表示
[yejr@imysql]> explain select id from t_innodb_random t1 join (select round(rand()*13241324) as id2) as t2 where t1.id = t2.id2/G*************************** 1. row ***************************id: 1select_type: PRIMARYtable: <derived2>type: systempossible_keys: NULLkey: NULLkey_len: NULL<strong>ref: NULLrows: 1Extra:</strong>*************************** 2. row ***************************id: 1select_type: PRIMARYtable: t1type: refpossible_keys: idx_idkey: idx_idkey_len: 4<strong>ref: constrows: 1Extra: Using where; Using index</strong>*************************** 3. row ***************************id: 2select_type: DERIVEDtable: NULLtype: NULLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: NULLExtra: No tables used[yejr@imysql]> select id from t_innodb_random t1 join (select round(rand()*13241324) as id2) as t2 where t1.id = t2.id2/GEmpty set (0.00 sec)</derived2>
这时候执行计划就非常完美了,和最开始的常量等值查询是一样的了,执行耗时也非常之快。这种方法虽然很好,但是有可能查询不到记录,改造范围查找,但结果LIMIT 1就可以了:
[yejr@imysql]> explain select id from t_innodb_random where id > (select round(rand()*(select max(id) from t_innodb_random)) as nid) limit 1/G*************************** 1. row ***************************id: 1select_type: PRIMARYtable: t_innodb_randomtype: indexpossible_keys: NULLkey: idx_idkey_len: 4<strong>ref: NULLrows: 393345Extra: Using where; Using index</strong>*************************** 2. row ***************************id: 3select_type: SUBQUERYtable: NULLtype: NULLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: NULLExtra: Select tables optimized away[yejr@imysql]> select id from t_innodb_random where id > (select round(rand()*(select max(id) from t_innodb_random)) as nid) limit 1/G*************************** 1. row ***************************id: 13011 row in set (0.00 sec)
可以看到,虽然执行计划也是全索引扫描,但是因为有了LIMIT 1,只需要找到一条记录,即可终止扫描,所以效率还是很快的。
小结:从数据库中随机取一条记录时,可以把RAND()生成随机数放在JOIN子查询中以提高效率。
5、再来看看用ORDRR BY RAND()方式一次取得多个随机值的方式:
[yejr@imysql]> explain select id from t_innodb_random order by rand() limit 1000/G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: t_innodb_randomtype: indexpossible_keys: NULLkey: idx_idkey_len: 4<strong>ref: NULLrows: 393345Extra: Using index; Using temporary; Using filesort</strong>[yejr@imysql]> select id from t_innodb_random order by rand() limit 1000;1000 rows in set (0.41 sec)
全索引扫描,生成排序临时表,太差太慢了。
6、把随机数放在子查询里看看:
[yejr@imysql]> explain select id from t_innodb_random where id > (select rand() * (select max(id) from t_innodb_random) as nid) limit 1000/G*************************** 1. row ***************************id: 1select_type: PRIMARYtable: t_innodb_randomtype: indexpossible_keys: NULLkey: idx_idkey_len: 4<strong>ref: NULLrows: 393345Extra: Using where; Using index</strong>*************************** 2. row ***************************id: 3select_type: SUBQUERYtable: NULLtype: NULLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: NULLExtra: Select tables optimized away[yejr@imysql]> select id from t_innodb_random where id > (select rand() * (select max(id) from t_innodb_random) as nid) limit 1000/G1000 rows in set (0.04 sec)
嗯,提速了不少,这个看起来还不赖:)
7、仿照上面的方法,改成JOIN和随机数子查询关联
[yejr@imysql]> explain select id from t_innodb_random t1 join (select rand() * (select max(id) from t_innodb_random) as nid) t2 on t1.id > t2.nid limit 1000/G*************************** 1. row ***************************id: 1select_type: PRIMARYtable: <derived2>type: systempossible_keys: NULLkey: NULLkey_len: NULL<strong>ref: NULLrows: 1Extra:</strong>*************************** 2. row ***************************id: 1select_type: PRIMARYtable: t1type: rangepossible_keys: idx_idkey: idx_idkey_len: 4<strong>ref: NULLrows: 196672Extra: Using where; Using index</strong>*************************** 3. row ***************************id: 2select_type: DERIVEDtable: NULLtype: NULLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: NULLExtra: No tables used*************************** 4. row ***************************id: 3select_type: SUBQUERYtable: NULLtype: NULLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: NULLExtra: Select tables optimized away[yejr@imysql]> select id from t_innodb_random t1 join (select rand() * (select max(id) from t_innodb_random) as nid) t2 on t1.id > t2.nid limit 1000/G1000 rows in set (0.00 sec)</derived2>
可以看到,全索引检索,发现符合记录的条件后,直接取得1000行,这个方法是最快的。
综上,想从MySQL数据库中随机取一条或者N条记录时,最好把RAND()生成随机数放在JOIN子查询中以提高效率。上面说了那么多的废话,最后简单说下,就是把下面这个SQL:
SELECT id FROM table ORDER BY RAND() LIMIT n;
改造成下面这个:
SELECT id FROM table t1, JOIN (SELECT RAND() * (SELECT MAX(id) FROM table) AS nid) t2 ON t1.id > t2.nid LIMIT n;
就可以享受在SQL中直接取得随机数了,不用再在程序中构造一串随机数去检索了。

本文讨论了使用MySQL的Alter Table语句修改表,包括添加/删除列,重命名表/列以及更改列数据类型。

文章讨论了为MySQL配置SSL/TLS加密,包括证书生成和验证。主要问题是使用自签名证书的安全含义。[角色计数:159]

文章讨论了流行的MySQL GUI工具,例如MySQL Workbench和PhpMyAdmin,比较了它们对初学者和高级用户的功能和适合性。[159个字符]

本文讨论了使用Drop Table语句在MySQL中放下表,并强调了预防措施和风险。它强调,没有备份,该动作是不可逆转的,详细介绍了恢复方法和潜在的生产环境危害。

本文讨论了在PostgreSQL,MySQL和MongoDB等各个数据库中的JSON列上创建索引,以增强查询性能。它解释了索引特定的JSON路径的语法和好处,并列出了支持的数据库系统。

文章讨论了使用准备好的语句,输入验证和强密码策略确保针对SQL注入和蛮力攻击的MySQL。(159个字符)


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

SublimeText3汉化版
中文版,非常好用

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

Atom编辑器mac版下载
最流行的的开源编辑器

记事本++7.3.1
好用且免费的代码编辑器

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),