MySQL 中随机选择10条记录

数据库去重 20-12-3

mysql手册中存在rand()命令,能获取到随机行, 并使用limit 10 只采取其中几行。

SELECTidFROMuserORDERBYRAND()LIMIT10;

数据量小于1000行的时候,上面的 sql 执行的快。但是当数据大于10000行, 排序的开销就变得很重。上面的操作中,我们在排序完就把几乎所有的行都丢掉了。

只要我们有一个数字主键,我们可以有更好的方式去实现这个功能,不需要对所有数据进行排序。

在上面的例子中, 我们假设 id 从1开始, 并且在1和 id 的最大值之间是连续的。

通过应用程序解决问题

可以在应用程序中计算随机id, 简化整个计算。

SELECTMAX(id)FROMuser;##在应用程序中生成区间内的随机数:random-id  SELECTnameFROMuserWHEREid=<random-id>

由于MAX(id) == COUNT(id),我们只是生成1和 max (id) 之间的随机数, 并将其传递到数据库中检索随机行。

第一个select语句是NO-OP,并一直在被优化。第二个是针对常量的 eq 速度也很快。

通过数据库解决问题

#生成一个随机ID>SELECTRAND()*MAX(id)FROMuser;+------------------+|RAND()*MAX(id)|+------------------+|689.37582507297|+------------------+#返回值是double,但是我们需要的是int>SELECTCEIL(RAND()*MAX(id))FROMuser;+-------------------------+|CEIL(RAND()*MAX(id))|+-------------------------+|1000000|+-------------------------+#返回值是int,分析性能>EXPLAIN  SELECTCEIL(RAND()*MAX(id))FROMrandom;+----+-------------+-------+-------+------+-------------+|id|select_type|table|type|rows|Extra|+----+-------------+-------+-------+------+-------------+|1|SIMPLE|random|index|1000000|Usingindex|+----+-------------+-------+-------+------+-------------+##全表扫描?由于使用MAX()函数了,导致优化丢失。>EXPLAIN  SELECTCEIL(RAND()*(SELECTMAX(id)FROMrandom));+----+-------------+-------+------+------+------------------------------+|id|select_type|table|type|rows|Extra|+----+-------------+-------+------+------+------------------------------+|1|PRIMARY|NULL|NULL|NULL|Notablesused||2|SUBQUERY|NULL|NULL|NULL|Selecttablesoptimizedaway|+----+-------------+-------+------+------+------------------------------+##子查询可以将性能损失挽回

通过上面的 sql 已经能够生成随机 id, 但如何获得行?

>EXPLAINSELECTnameFROMuserWHEREid=(SELECTCEIL(RAND()*  (SELECTMAX(id)  FROMuser));+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+|1|PRIMARY|user|ALL|NULL|NULL|NULL|NULL|1000000|Usingwhere||3|SUBQUERY|NULL|NULL|NULL|NULL|NULL|NULL|NULL|Selecttablesoptimizedaway|+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+>showwarnings;+-------+------+------------------------------------------+|Level|Code|Message|+-------+------+------------------------------------------+|Note|1249|Select2wasreducedduringoptimization|+-------+------+------------------------------------------+

上面的方法是最明显的, 但也是最错误的做法。原因是:where子查询中的select为外部select每一行都会执行。具体解释参考:sql语句嵌套查询性能低

要找一种方法,保证random-id只生成一次:

SELECTnameFROMuserJOIN  (SELECTCEIL(RAND()*  (SELECTMAX(id)  FROMuser))ASid)ASr2USING(id);+----+-------------+------------+--------+------+------------------------------+|id|select_type|table|type|rows|Extra|+----+-------------+------------+--------+------+------------------------------+|1|PRIMARY|<derived2>|system|1|||1|PRIMARY|user|const|1|||2|DERIVED|NULL|NULL|NULL|Notablesused||3|SUBQUERY|NULL|NULL|NULL|Selecttablesoptimizedaway|+----+-------------+------------+--------+------+------------------------------+

内部select生成一个常量临时表, join 只在单行上执行。没有使用排序,没有通过应用程序,查询的大多数部分都被优化了。

非连续数据

删除一些行,构造ID非连续的记录。

SELECTnameFROMrandomASr1JOIN  (SELECT(RAND()*  (SELECTMAX(id)  FROMrandom))ASid)  ASr2WHEREr1.id>=r2.idORDERBYr1.idASC  LIMIT1;+----+-------------+------------+--------+------+------------------------------+|id|select_type|table|type|rows|Extra|+----+-------------+------------+--------+------+------------------------------+|1|PRIMARY|<derived2>|system|1|||1|PRIMARY|r1|range|689|Usingwhere||2|DERIVED|NULL|NULL|NULL|Notablesused||3|SUBQUERY|NULL|NULL|NULL|Selecttablesoptimizedaway|+----+-------------+------------+--------+------+------------------------------+

join现在获取所有大于或等于我们随机值的ID,如果不能直接匹配则选择邻居。 但是一旦找到一行,就停止执行(LIMIT 1)。根据索引(ORDER BY id ASC)读取行。 当使用 >= 而不是a = 时,我们可以摆脱CEIL并以更少的工作获得相同的结果。

平等分配

当我们的ID分布不再相等时,我们选择的行也不是真正随机的。

>select*fromholes;+----+----------------------------------+----------+|id|name|accesses|+----+----------------------------------+----------+|1|d12b2551c6cb7d7a64e40221569a8571|107||2|f82ad6f29c9a680d7873d1bef822e3e9|50||4|9da1ed7dbbdcc6ec90d6cb139521f14a|132||8|677a196206d93cdf18c3744905b94f73|230||16|b7556d8ed40587a33dc5c449ae0345aa|481|+----+----------------------------------+----------+

RAND方法会生成9到15之类的ID,这些ID都会导致id 16被选为下一个更高的数字。

这个问题没有真正的解决方案,但是由于你的数据大多是不变的,你可以添加一个映射表,将行号映射到id:

>createtableholes_map(row_idintnotNULLprimarykey,random_idintnotnull);>SET@id=0;>INSERTINTOholes_mapSELECT@id:=@id+1,idFROMholes;>select*fromholes_map;+--------+-----------+|row_id|random_id|+--------+-----------+|1|1||2|2||3|4||4|8||5|16|+--------+-----------+

row_id现在再次是连续,我们可以再次运行随机查询

SELECTnameFROMholesJOIN(SELECTr1.random_idFROMholes_mapASr1JOIN(SELECT(RAND()*  (SELECTMAX(row_id)  FROMholes_map))ASrow_id)  ASr2WHEREr1.row_id>=r2.row_idORDERBYr1.row_idASC  LIMIT1)asrowsON(id=random_id);

1000次提取后,我们再次看到平均分布:

>select*fromholes;+----+----------------------------------+----------+|id|name|accesses|+----+----------------------------------+----------+|1|d12b2551c6cb7d7a64e40221569a8571|222||2|f82ad6f29c9a680d7873d1bef822e3e9|187||4|9da1ed7dbbdcc6ec90d6cb139521f14a|195||8|677a196206d93cdf18c3744905b94f73|207||16|b7556d8ed40587a33dc5c449ae0345aa|189|+----+----------------------------------+----------+

维护连续的表

DROPTABLEIFEXISTSr2;CREATETABLEr2(  idSERIAL,  nameVARCHAR(32)NOTNULLUNIQUE);DROPTABLEIFEXISTSr2_equi_dist;CREATETABLEr2_equi_dist(  idSERIAL,  r2_idbigintunsignedNOTNULLUNIQUE);

当我们在r2中更改某些内容时,我们希望r2_equi_dist也会更新。

DELIMITER$$DROPTRIGGERIFEXISTStai_r2$$CREATETRIGGERtai_r2AFTERINSERTONr2FOREACHROWBEGIN  DECLAREmBIGINTUNSIGNEDDEFAULT1;    SELECTMAX(id)+1FROMr2_equi_distINTOm;  SELECTIFNULL(m,1)INTOm;  INSERTINTOr2_equi_dist(id,r2_id)VALUES(m,NEW.id);END$$DELIMITER;DELETEFROMr2;INSERTINTOr2VALUES(NULL,MD5(RAND()));INSERTINTOr2VALUES(NULL,MD5(RAND()));INSERTINTOr2VALUES(NULL,MD5(RAND()));INSERTINTOr2VALUES(NULL,MD5(RAND()));SELECT*FROMr2;+----+----------------------------------+|id|name|+----+----------------------------------+|1|8b4cf277a3343cdefbe19aa4dabc40e1||2|a09a3959d68187ce48f4fe7e388926a9||3|4e1897cd6d326f8079108292376fa7d5||4|29a5e3ed838db497aa330878920ec01b|+----+----------------------------------+SELECT*FROMr2_equi_dist;+----+-------+|id|r2_id|+----+-------+|1|1||2|2||3|3||4|4|+----+-------+

INSERT非常简单,DELETE操作我们必须更新equi-dist-id以保持id的连续设置:

DELIMITER$$DROPTRIGGERIFEXISTStad_r2$$CREATETRIGGERtad_r2AFTERDELETEONr2FOREACHROWBEGIN  DELETEFROMr2_equi_distWHEREr2_id=OLD.id;  UPDATEr2_equi_distSETid=id-1WHEREr2_id>OLD.id;END$$DELIMITER;DELETEFROMr2WHEREid=2;SELECT*FROMr2;+----+----------------------------------+|id|name|+----+----------------------------------+|1|8b4cf277a3343cdefbe19aa4dabc40e1||3|4e1897cd6d326f8079108292376fa7d5||4|29a5e3ed838db497aa330878920ec01b|+----+----------------------------------+SELECT*FROMr2_equi_dist;+----+-------+|id|r2_id|+----+-------+|1|1||2|3||3|4|+----+-------+

update操作需要维护外键约束:

DELIMITER$$DROPTRIGGERIFEXISTStau_r2$$CREATETRIGGERtau_r2AFTERUPDATEONr2FOREACHROWBEGIN  UPDATEr2_equi_distSETr2_id=NEW.idWHEREr2_id=OLD.id;END$$DELIMITER;UPDATEr2SETid=25WHEREid=4;SELECT*FROMr2;+----+----------------------------------+|id|name|+----+----------------------------------+|1|8b4cf277a3343cdefbe19aa4dabc40e1||3|4e1897cd6d326f8079108292376fa7d5||25|29a5e3ed838db497aa330878920ec01b|+----+----------------------------------+SELECT*FROMr2_equi_dist;+----+-------+|id|r2_id|+----+-------+|1|1||2|3||3|25|+----+-------+

一次多行

如果要返回多行,您可以:

  • 多次执行查询

  • 编写执行查询的存储过程并将结果存储在临时表中

存储过程

存储过程为你了程序语言结构:

  • 循环

  • 控制结构

对于此任务,我们只需要一个循环:

ELIMITER$$DROPPROCEDUREIFEXISTSget_rands$$CREATEPROCEDUREget_rands(INcntINT)BEGIN  DROPTEMPORARYTABLEIFEXISTSrands;  CREATETEMPORARYTABLErands(rand_idINT);loop_me:LOOP  IFcnt<1THEN  LEAVEloop_me;  ENDIF;    INSERTINTOrandsSELECTr1.idFROMrandomASr1JOIN  (SELECT(RAND()*  (SELECTMAX(id)  FROMrandom))ASid)  ASr2WHEREr1.id>=r2.idORDERBYr1.idASC  LIMIT1;    SETcnt=cnt-1;  ENDLOOPloop_me;END$$DELIMITER;CALLget_rands(4);SELECT*FROMrands;+---------+|rand_id|+---------+|133716||702643||112066||452400|+---------+

性能

我们有3个不同的查询来解决我们的问题:

  • Q1. ORDER BY RAND()

  • Q2. RAND() * MAX(ID)

  • Q3. RAND() * MAX(ID) + ORDER BY ID

1001.00010.000100.0001.000.000  Q10:00.718s0:02.092s0:18.684s2:59.081s58:20.000s  Q20:00.519s0:00.607s0:00.614s0:00.628s0:00.637s  Q30:00.570s0:00.607s0:00.614s0:00.628s0:00.637s

正如您所看到的那样,简单的ORDER BY RAND()已经落后于表中仅100 行的优化查询。

免责声明:
1.资讯内容不构成投资建议,投资者应独立决策并自行承担风险
2.本文版权归属原作所有,仅代表作者本人观点,不代表本站的观点或立场
上一篇:php获取远程图片并下载保存到本地的方法分析

相关资讯