《Mysql入门如何提高MySQL RAND随机排序效率》要点: 本文介绍了Mysql入门如何提高MySQL RAND随机排序效率,希望对您有用。如果有疑问,可以联系我们。
导读:在MySQL中,如果直接 ORDER BY RAND() 的话,效率非常差,那么如何进行优化呢??众所周知,在MySQL中,如果直接 ORDER BY RAND() ...
在MySQL中,那么如何进行优化呢??
众所周知,因为会多次执行.事实上,如果等值查询也是用 RAND() 的话也如此,我们先来看看下面这几个SQL的不同执行计划和执行耗时.
首先,看下建表DDL,这是一个没有显式自增主键的InnoDB表:
- [yejr@imysql]>?show?create?table?t_innodb_randomG?
- ***************************?1.?row***************************?
- Table:t_innodb_random?
- Create?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 字段也是乱序的.
?MYSQL必读
- [yejr@imysql]>?select?count(*)?from?t_innodb_randomG?
- ***************************?1.?row***************************?
- count(*):?393216?
1、常量等值检索
?MYSQL必读
- [yejr@imysql]>?explain?select?id?fromt_innodb_random?where??
- ?id?=?13412G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:?SIMPLE?
- table:t_innodb_random?
- type:?ref?
- possible_keys:?idx_id?
- key:?idx_id?
- key_len:?4?
- ref:?const?
- rows:?1?
- Extra:?Using?index?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_random??
- ?where?id?=13412;?
- 1?row?in?set?(0.00?sec)?
可以看到执行计划很不错,是常量等值查询,速度非常快.
2、使用RAND()函数乘以常量,求得随机数后检索MYSQL必读
- [yejr@imysql]>?explain?select?id?from?t_innodb_random?where??
- ?id?=?round(rand()*13241324)G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:?SIMPLE?
- table:t_innodb_random?
- type:?index?
- possible_keys:?NULL?
- key:?idx_id?
- key_len:?4?
- ref:?NULL?
- rows:?393345?
- Extra:?Using?where;?Using?index?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_random?where??
- ?id?=?round(rand()*13241324)G?
- Empty?set?(0.26?sec)?
可以看到执行计划很糟糕,虽然只扫描索引,但却是全索引扫描,效率非常差.因为WHERE条件中包含了RAND(),使得MySQL把它当做变量来处理,无法用常量等值的方式查询,效率很低.
我们把常量改成取t_innodb_random表的最大id值,再乘以RAND()求得随机数后检索看看什么情况:MYSQL必读
- [yejr@imysql]>?explain?select?id?from?t_innodb_random?where??
- ?id?=?round(rand()*(select?max(id)?from?t_innodb_random))G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:PRIMARY?
- table:t_innodb_random?
- type:?index?
- possible_keys:?NULL?
- key:?idx_id?
- key_len:?4?
- ref:?NULL?
- rows:?393345?
- Extra:?Using?where;?Using?index?
- ***************************?2.?row***************************?
- id:?2?
- select_type:SUBQUERY?
- table:?NULL?
- type:?NULL?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?NULL?
- Extra:?Selecttables?optimized?away?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_random?where??
- ?id?=?round(rand()*(select?max(id)?from?t_innodb_random))G?
- Empty?set?(0.27?sec)?
可以看到,执行计划依然是全索引扫描,执行耗时也基本相当.
3、改造成普通子查询模式,这里有两次子查询
?MYSQL必读
- [yejr@imysql]>?explain?select?id?fromt_innodb_random?where??
- ?id?=?(select?round(rand()*(select?max(id)?fromt_innodb_random))?as?nid)G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:PRIMARY?
- table:t_innodb_random?
- type:?index?
- possible_keys:?NULL?
- key:?idx_id?
- key_len:?4?
- ref:?NULL?
- rows:?393345?
- Extra:?Using?where;?Using?index?
- ***************************?2.?row***************************?
- id:?3?
- select_type:SUBQUERY?
- table:?NULL?
- type:?NULL?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?NULL?
- Extra:?Selecttables?optimized?away?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_random?where??
- ?id?=?(select?round(rand()*(select?max(id)?from?t_innodb_random))?as?nid)G?
- Empty?set?(0.27?sec)?
可以看到,执行计划也不好,执行耗时较慢.
4、改造成JOIN关联查询,不过最大值还是用常量表示
?MYSQL必读
- ?
- ?
- [yejr@imysql]>?explain?select?id?fromt_innodb_random?t1?join??
- ?(select?round(rand()*13241324)?as?id2)?as?t2?wheret1.id?=?t2.id2G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:PRIMARY?
- table:<derived2>?
- type:?system?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?1?
- Extra:?
- ***************************?2.?row***************************?
- id:?1?
- select_type:PRIMARY?
- table:?t1?
- type:?ref?
- possible_keys:?idx_id?
- key:?idx_id?
- key_len:?4?
- ref:?const?
- rows:?1?
- Extra:?Using?where;?Using?index?
- ***************************?3.?row***************************?
- id:?2?
- select_type:DERIVED?
- table:?NULL?
- type:?NULL?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?NULL?
- Extra:?Notables?used?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_random?t1?join??
- ?(select?round(rand()*13241324)?as?id2)?as?t2?where?t1.id?=t2.id2G?
- Empty?set?(0.00?sec)?
这时候执行计划就非常完美了,和最开始的常量等值查询是一样的了,执行耗时也非常之快.
这种方法虽然很好,但是有可能查询不到记录,改造范围查找,但结果LIMIT 1就可以了:
?MYSQL必读
- [yejr@imysql]>?explain?select?id?fromt_innodb_random?where??
- ?id?>?(select?round(rand()*(select?max(id)?fromt_innodb_random))?as?nid)??
- ?limit?1G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:PRIMARY?
- table:t_innodb_random?
- type:?index?
- possible_keys:?NULL?
- key:?idx_id?
- key_len:?4?
- ref:?NULL?
- rows:?393345?
- Extra:?Using?where;?Using?index?
- ***************************?2.?row***************************?
- id:?3?
- select_type:SUBQUERY?
- table:?NULL?
- type:?NULL?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?NULL?
- Extra:?Selecttables?optimized?away?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_random?where??
- ?id?>?(select?round(rand()*(select?max(id)?from?t_innodb_random))?asnid)??
- ?limit?1G?
- ***************************?1.?row***************************?
- id:?1301?
- 1?row?in?set?(0.00?sec)?
可以看到,虽然执行计划也是全索引扫描,但是因为有了LIMIT 1,只需要找到一条记录,即可终止扫描,所以效率还是很快的.
小结:从数据库中随机取一条记录时,可以把RAND()生成随机数放在JOIN子查询中以提高效率.
5、再来看看用ORDRRBY RAND()方式一次取得多个随机值的方式
?MYSQL必读
- [yejr@imysql]>?explain?select?id?from?t_innodb_random?order?by?rand()?limit?1000G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:?SIMPLE?
- table:t_innodb_random?
- type:?index?
- possible_keys:?NULL?
- key:?idx_id?
- key_len:?4?
- ref:?NULL?
- rows:?393345?
- Extra:?Using?index;?Using?temporary;?Using?filesort?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_randomorder?by?rand()?limit?1000;?
- 1000?rows?in?set?(0.41?sec)?
全索引扫描,生成排序临时表,太差太慢了.
6、把随机数放在子查询里看看
?MYSQL必读
- [yejr@imysql]>?explain?select?id?fromt_innodb_random?where??
- ?id?>?(select?rand()?*?(select?max(id)?fromt_innodb_random)?as?nid)??
- ?limit?1000G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:PRIMARY?
- table:t_innodb_random?
- type:?index?
- possible_keys:?NULL?
- key:?idx_id?
- key_len:?4?
- ref:?NULL?
- rows:?393345?
- Extra:?Using?where;?Using?index?
- ***************************?2.?row?***************************?
- id:?3?
- select_type:SUBQUERY?
- table:?NULL?
- type:?NULL?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?NULL?
- Extra:?Selecttables?optimized?away?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_random?where??
- ?id?>?(select?rand()?*?(select?max(id)?from?t_innodb_random)?as?nid)?
- ?limit?1000G?
- 1000?rows?in?set?(0.04?sec)?
嗯,提速了不少,这个看起来还不赖:)
7、仿照上面的方法,改成JOIN和随机数子查询关联
?MYSQL必读
- [yejr@imysql]>?explain?select?id?fromt_innodb_random?t1?join??
- ?(select?rand()?*?(select?max(id)?from?t_innodb_random)as?nid)?t2?on??
- ?t1.id?>?t2.nid?limit?1000G?
- ***************************?1.?row***************************?
- id:?1?
- select_type:PRIMARY?
- table:<derived2>?
- type:?system?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?1?
- Extra:?
- ***************************?2.?row***************************?
- id:?1?
- select_type:PRIMARY?
- table:?t1?
- type:?range?
- possible_keys:?idx_id?
- key:?idx_id?
- key_len:?4?
- ref:?NULL?
- rows:?196672?
- Extra:?Using?where;?Using?index?
- ***************************?3.?row***************************?
- id:?2?
- select_type:DERIVED?
- table:?NULL?
- type:?NULL?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?NULL?
- Extra:?Notables?used?
- ***************************?4.?row***************************?
- id:?3?
- select_type:SUBQUERY?
- table:?NULL?
- type:?NULL?
- possible_keys:?NULL?
- key:?NULL?
- key_len:?NULL?
- ref:?NULL?
- rows:?NULL?
- Extra:?Selecttables?optimized?away?
- ?
- [yejr@imysql]>?select?id?from?t_innodb_randomt1?join?(select?rand()?*?(select?max(id)?from?t_innodb_random)?as?nid)?t2?ont1.id?>?t2.nid?limit?1000G?
- 1000?rows?in?set?(0.00?sec)?
可以看到,全索引检索,发现符合记录的条件后,直接取得1000行,这个方法是最快的.
综上,想从MySQL数据库中随机取一条或者N条记录时,最好把RAND()生成随机数放在JOIN子查询中以提高效率.
简言之,就是把下面这个SQL:
SELECT id FROM table ORDER BY RAND() LIMIT n;
改造成下面这个:
SELECT id FROM table t1 JOIN
(SELECT RAND() * (SELECTMAX(id) FROM table) AS nid) t2
ON t1.id > t2.nid LIMIT n;
就可以享受在SQL中直接取得随机数了,不用再在程序中构造一串随机数去检索了.
?MYSQL必读 (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|