《Mysql入門如何提高MySQL RAND隨機排序效率》要點:
本文介紹了Mysql入門如何提高MySQL RAND隨機排序效率,希望對您有用。如果有疑問,可以聯系我們。
- [yejr@imysql]>?show?create?table?t_innodb_random\G?
- ***************************?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_random\G?
- ***************************?1.?row***************************?
- count(*):?393216?
1、常量等值檢索
?MYSQL必讀
- [yejr@imysql]>?explain?select?id?fromt_innodb_random?where??
- ?id?=?13412\G?
- ***************************?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必讀
- //維易PHP培訓網?www.bcty365.com?
- ?
- [yejr@imysql]>?explain?select?id?fromt_innodb_random?t1?join??
- ?(select?round(rand()*13241324)?as?id2)?as?t2?wheret1.id?=?t2.id2\G?
- ***************************?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.id2\G?
- 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?1\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))?asnid)??
- ?limit?1\G?
- ***************************?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?1000\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?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?1000\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?rand()?*?(select?max(id)?from?t_innodb_random)?as?nid)?
- ?limit?1000\G?
- 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?1000\G?
- ***************************?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?1000\G?
- 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必讀