改进MySQL Order By Rand()的低效率

本文深入探讨了MySQL中随机抽取数据的多种方法,包括原始的RAND()方法、改进的JOIN方法以及通过SQL语句预先随机ID序列的高效查询方法。详细比较了各方法的性能差异,推荐了效率最高且IO开销最小的解决方案。

正 文:

    最近由于需要研究了一下MYSQL的随机抽取实现方法。举个例子,要从tablename表中随机提取一条记录,大家一般的写法就是:
SELECT * FROM content ORDER BY RAND() LIMIT 1

飘易注:3万条记录查询花费 0.3745 秒(下同);从mysql slow query log看出“ORDER BY RAND() ”全表扫描了2次!

后来我查了一下MYSQL的官方手册,里面针对RAND()的提示大概意思就是,在ORDER BY从句里面不能使用RAND()函数,因为这样会导致数据列被多次扫描。但是在MYSQL 3.23版本中,仍然可以通过ORDER BY RAND()来实现随机。

但是真正测试一下才发现这样效率非常低。一个15万余条的库,查询5条数据,居然要8秒以上。查看官方手册,也说rand()放在ORDER BY 子句中会被执行多次,自然效率及很低。

搜索Google,采用JOIN,查询max(id) * rand()来随机获取数据。
SELECT * 
FROM `content` AS t1 JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `content`)) AS id) AS t2 
WHERE t1.id >= t2.id 
ORDER BY t1.id ASC LIMIT 1; 

查询花费 0.0008 秒,飘易认为可以推荐使用这个语句!!

但是这样会产生连续的5条记录。解决办法只能是每次查询一条,查询5次。即便如此也值得,因为15万条的表,查询只需要0.01秒不到。

有一个方法:
SELECT * FROM `content` AS a JOIN ( SELECT MAX( ID ) AS ID FROM `content` ) AS b ON ( a.ID >= FLOOR( b.ID * RAND( ) ) ) LIMIT 5;  

上面这种方式保证了一定范围内的随机,查询花费 0.4265 秒,也不推荐。

下面的语句,mysql的论坛上有人使用
SELECT * 
FROM `content` 
WHERE id >= (SELECT FLOOR( MAX(id) * RAND()) FROM `content` ) 
ORDER BY id LIMIT 1;
  
查询花费 1.2254 秒,飘易强烈不推荐!因为实测后,3万行的表,这个语句竟然会扫描500万行!!

跟上面的语句还是有很大差距。总觉有什么地方不正常。于是我把语句改写了一下。
SELECT * FROM `content` 
WHERE id >= (SELECT floor(RAND() * (SELECT MAX(id) FROM `content`)))  
ORDER BY id LIMIT 1; 
 
查询花费 0.0012 秒

这下,效率又提高了,查询时间只有0.01秒

最后,再把语句完善一下,加上MIN(id)的判断。我在最开始测试的时候,就是因为没有加上MIN(id)的判断,结果有一半的时间总是查询到表中的前面几行。
完整查询语句是:
SELECT * FROM `content` 
WHERE id >= (SELECT floor( RAND() * ((SELECT MAX(id) FROM `content`)-(SELECT MIN(id) FROM `content`)) + (SELECT MIN(id) FROM `content`)))  
ORDER BY id LIMIT 1; 
 
查询花费 0.0012 秒

SELECT * 
FROM `content` AS t1 JOIN (SELECT ROUND(RAND() * ((SELECT MAX(id) FROM `content`)-(SELECT MIN(id) FROM `content`))+(SELECT MIN(id) FROM `content`)) AS id) AS t2 
WHERE t1.id >= t2.id 
ORDER BY t1.id LIMIT 1;
   
查询花费 0.0008 秒

最后在php中对这两个语句进行分别查询10次,
前者花费时间 0.147433 秒
后者花费时间 0.015130 秒
看来采用JOIN的语法比直接在WHERE中使用函数效率还要高很多。( via

======================================
【好了,最后飘易来总结下】
第一种方案,即原始的 Order By Rand() 方法
$sql="SELECT * FROM content ORDER BY rand() LIMIT 12";
$result=mysql_query($sql,$conn);
$n=1;
$rnds='';
while($row=mysql_fetch_array($result)){
$rnds=$rnds.$n.". <a href='show".$row['id']."-".strtolower(trim($row['title']))."'>".$row['title']."</a><br />\n";
$n++;
}

3万条数据查12条随机记录,需要0.125秒,随着数据量的增大,效率越来越低。

第二种方案,改进后的 JOIN 方法
for($n=1;$n<=12;$n++){
$sql="SELECT * FROM `content` AS t1 
JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `content`)) AS id) AS t2 
WHERE t1.id >= t2.id ORDER BY t1.id ASC LIMIT 1";
$result=mysql_query($sql,$conn);
$yi=mysql_fetch_array($result);
$rnds = $rnds.$n.". <a href='show".$yi['id']."-".strtolower(trim($yi['title']))."'>".$yi['title']."</a><br />\n";
}

3万条数据查12条随机记录,需要0.004秒,效率大幅提升,比第一种方案提升了约30倍。缺点:多次select查询,IO开销大。

第三种方案,SQL语句先随机好ID序列,用 IN 查询(飘易推荐这个用法,IO开销小,速度最快)
$sql="SELECT MAX(id),MIN(id) FROM content";
$result=mysql_query($sql,$conn);
$yi=mysql_fetch_array($result);
$idmax=$yi[0];
$idmin=$yi[1];
$idlist='';    
for($i=1;$i<=20;$i++){    
if($i==1){ $idlist=mt_rand($idmin,$idmax); }    
else{ $idlist=$idlist.','.mt_rand($idmin,$idmax); }    
}  
$idlist2="id,".$idlist;
$sql="select * from content where id in ($idlist) order by field($idlist2) LIMIT 0,12"; 
$result=mysql_query($sql,$conn);
$n=1;
$rnds='';
while($row=mysql_fetch_array($result)){
$rnds=$rnds.$n.". <a href='show".$row['id']."-".strtolower(trim($row['title']))."'>".$row['title']."</a><br />\n";
$n++;
}

3万条数据查12条随机记录,需要0.001秒,效率比第二种方法又提升了4倍左右,比第一种方法提升120倍。注,这里使用了 order by field($idlist2) 是为了不排序,否则 IN 是自动会排序的。缺点:有可能遇到ID被删除的情况,所以需要多选几个ID。

测试方法:
$t = microtime(true); 
//执行语句
echo microtime(true) - $t;  
<think>好的,我现在需要解释一下SQL中的`ORDER BY RAND()`。用户可能之前已经问过关于`LIMIT 1 OFFSET 1`的问题,现在转向随机排序。首先,我应该回忆这个函数的基本用法和作用。 用户可能想知道`ORDER BY RAND()`的功能,比如它是如何实现随机排序的,以及在实际应用中的常见用途,比如随机抽样数据。我需要解释清楚它的工作机制,比如每次执行时如何生成随机数并排序结果。 然后,我需要考虑使用场景。用户提到过随机抽样的例子,可能需要更详细的说明,比如如何结合`LIMIT`来获取随机的一条或多条记录。同时,可能需要举一个具体的例子,比如从用户表中随机选择一名用户,这样更直观。 接下来,注意事项部分很重要。用户可能不了解`ORDER BY RAND()`的性能问题,尤其是在处理大数据量时,因为它需要对所有数据进行随机排序,这会导致性能下降。我需要解释为什么会有这个问题,并提供替代方案,比如使用应用程序端的随机处理或特定数据库的函数。 另外,不同数据库的差异也需要提及。比如MySQL、PostgreSQL和SQLite可能都支持`RAND()`,但其他数据库如SQL Server使用`NEWID()`,而PostgreSQL还可能用`RANDOM()`。需要指出这些差异,帮助用户避免跨数据库时的兼容性问题。 还要注意语法细节,比如在`ORDER BY`子句中使用`RAND()`的正确方式,是否需要别名或者括号。例如,在有些情况下可能需要`ORDER BY RAND()`,而在其他情况下可能需要`RAND()`作为表达式。 用户可能存在的误区需要澄清,比如多次执行`ORDER BY RAND()`可能会导致不同的结果,这在某些情况下可能是期望的,但在需要确定性结果时可能不适用。同时,随机排序的效率问题,尤其是在大表中,可能需要更优的解决方案。 最后,总结部分需要简明扼要,强调`ORDER BY RAND()`的适用场景和局限性,帮助用户权衡使用。可能还需要提到在分页或需要稳定排序时的替代方法,比如使用固定的种子或者预先计算的随机值列。 需要确保回答结构清晰,分点说明,使用示例和注意事项来增强理解。同时,避免使用Markdown格式,保持口语化,但根据用户之前的回复,可能更倾向于结构化且带有代码块和示例的详细解释。需要检查是否有遗漏的重要点,比如安全方面的考虑或者与其他子句的结合使用。 现在整理这些思路,确保覆盖功能、使用场景、示例、注意事项、数据库差异和总结,同时保持逻辑连贯,逐步深入,帮助用户全面理解`ORDER BY RAND()`的作用和最佳实践。</think>#### SQL中的 `ORDER BY RAND()` 详解 --- ##### 1. **基本功能** `ORDER BY RAND()` 是SQL中用于对查询结果进行**随机排序**的子句。其核心逻辑是: - 为每一行生成一个随机数(通常是0到1之间的浮点数)。 - 根据这些随机数对结果集进行排序,从而实现数据的随机排列。 --- ##### 2. **典型使用场景** ###### ① 随机抽样数据 - 从表中随机选取一条记录: ```sql SELECT * FROM users ORDER BY RAND() LIMIT 1; ``` - 随机选取多条记录(如3条): ```sql SELECT * FROM products ORDER BY RAND() LIMIT 3; ``` ###### ② 随机展示内容 - 在网页或应用中随机显示广告、推荐商品、抽奖用户等场景。 ###### ③ 数据去偏分析 - 在数据分析时,通过随机排序避免因固定顺序导致的统计偏差。 --- ##### 3. **具体示例** 假设表 `students` 包含以下数据: | id | name | |----|--------| | 1 | Alice | | 2 | Bob | | 3 | Carol | | 4 | Dave | 执行以下查询: ```sql SELECT * FROM students ORDER BY RAND(); ``` **可能的输出**(每次执行结果不同): | id | name | |----|--------| | 3 | Carol | | 1 | Alice | | 4 | Dave | | 2 | Bob | --- ##### 4. **注意事项** ###### ① **性能问题** - **大表慎用**:`ORDER BY RAND()` 需要对全表数据生成随机数并排序,数据量较大时效率极低。 **优化方案**: - 对已知连续ID的表,使用随机范围查询(如 `WHERE id >= RAND() * MAX(id)`)。 - 预存随机值列:新增一列存储随机数,定期更新并基于此列排序。 - 使用数据库特定函数(如MySQL的 `RAND(N)` 配合种子)。 ###### ② **随机性范围** - 若结合 `LIMIT` 使用,需注意:当表中数据更新时,多次执行可能返回重复结果。 ```sql -- 假设表中新增一行后,可能再次抽到已出现过的数据 SELECT * FROM logs ORDER BY RAND() LIMIT 1; ``` ###### ③ **数据库兼容性** - **MySQL/PostgreSQL/SQLite**:支持 `RAND()`。 - **SQL Server**:使用 `NEWID()` 实现随机排序: ```sql SELECT TOP 1 * FROM users ORDER BY NEWID(); ``` - **Oracle**:需用 `DBMS_RANDOM.VALUE` 生成随机数: ```sql SELECT * FROM employees ORDER BY DBMS_RANDOM.VALUE FETCH FIRST 1 ROW ONLY; ``` --- ##### 5. **替代方案** ###### ① **应用程序端随机化** - 先查询全部ID,在程序代码中随机选取,再按ID查询具体数据: ```sql -- 第一步:获取所有ID SELECT id FROM products; -- 第二步(程序端随机选3个ID) SELECT * FROM products WHERE id IN (3,7,12); ``` ###### ② **使用固定种子** - 若需“可重复的随机”,可指定随机种子(部分数据库支持): ```sql -- MySQL示例(种子为123) SELECT * FROM users ORDER BY RAND(123) LIMIT 1; ``` --- ##### 6. **总结** - **适用场景**:小数据量随机抽样、动态内容展示。 - **避免场景**:大数据表、高频查询、严格性能要求的场景。 - **优化建议**:根据数据库类型选择高效随机方法,或结合程序端处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值