关于全面禁止直接在数据库内使用rand()进行随机读取的公告

管理员 zsx 2015-10-6 8595

很多开发者喜欢用类似以下的代码来取得随机文章:
$array = $zbp->GetArticleList(array('*'),$where,array('rand()'=>' '),array($numm),'');
这样做有以下问题:
1. 不支持MySQL之外的数据库;
2. 数据量上万即奇慢无比。

所以,除非特殊情况,我们今后不再允许代码含直接在数据库内rand()的应用通过审核。当然,不包含在PHP里用rand()或mt_rand()函数,也不包含Math.random()。

为什么会慢?
我这里尝试用非计算机专业术语来解释。
我们把一本书比作数据库。一般的数据库查询,比如查一条ID为1的记录,一般会直接根据索引来得到结果。索引的用途,相当于一本书的目录。直接查目录得到结果,自然会比一页一页翻来得快。
然而,ORDER BY RAND()呢?它会先给你这本书的每一页都随机写一个数字,这就要翻过每一页了。接着,再排序比大小,把每一页的数字都排序排一遍,这就又要把书翻一遍。再取出数字最小的那几条记录。所以,这就是慢的原因。(当然,MySQL的内部实现我没看过,这两个任务不一定是互相独立的。)
如果要比较专业解释的话,请参看MySQL的官方文档:

https://dev.mysql.com/doc/refman/5.0/en/mathematical-functions.html#function_rand
You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times. However, you can retrieve rows in random order like this:

mysql> SELECT * FROM tbl_name ORDER BY RAND();
ORDER BY RAND() combined with LIMIT is useful for selecting a random sample from a set of rows:

mysql> SELECT * FROM table1, table2 WHERE a=b AND c<d -> ORDER BY RAND() LIMIT 1000;
RAND() is not meant to be a perfect random generator. It is a fast way to generate random numbers on demand that is portable between platforms for the same MySQL version.

如何解决?
非对数据库有一定研究的开发者,请直接去除随机文章功能。
否则,请参考以下链接对你的程序进行优化。另外,请务必确保你的代码对SQLite等数据库有效,或干脆对这些数据库不提供相关功能。
在 MySQL 中,从 10 万条主键不连续的数据里随机取 3000 条,如何做到高效?

MySQL select 10 random rows from 600K rows fast


其它:我的测试数据库是六十万,大小3.1GB。



不理世事

最新回复 (6)
全部楼主
  • 管理员 washun 2015-10-6 0
    引用 2
  • 超级版主 天兴工作室 2015-10-6 0
    引用 3
    ✌✌✌

    天兴工作室 zblog模板主题定制

  • 用户 muxu332 2015-10-7 0
    引用 4
    支持楼主,好贴!
  • 用户 muxu332 2015-10-8 0
    引用 5
    支持!!!!
  • 用户 peakhour 2015-10-14 0
    引用 6
    本帖最后由 peakhour 于 2015-10-15 15:53 编辑

    微软有篇技术文章有写:
    https://msdn.microsoft.com/zh-tw/library/cc441928.aspx用BINARY_CHECKSUM的方法,如下例子。
      SELECT * FROM Table1
      WHERE (ABS(CAST(
      (BINARY_CHECKSUM(*) *
      RAND()) as int)) % 100) < 10

    我把它改写成适合我们zBlog的例子,如下:

    SELECT top 10 * FROM blog_Article 
    WHERE [log_Level]=4 AND [log_Type]=0
    AND   (ABS(CAST(
      (BINARY_CHECKSUM
      ([log_ID], NEWID())) as int))
      % 2) =1

    效能差别甚钜。


    2015/10/15更正:以上算法还是分配不均匀,还不如用这个:

    SELECT  TOP 10 * FROM [blog_Article]  TABLESAMPLE (0.1 percent) 
    WHERE [log_Level]=4 AND [log_Type]=0
    ORDER BY NEWID()



  • 应用开发者 懿古今 2017-4-26 0
    引用 7
    PHP里获取随机数还是可以的,在数据库里只能放弃使用了
    • ZBlogger技术交流中心
      8
          
返回