【问题标题】:Random draw without replacement随机抽奖,无需更换
【发布时间】:2012-11-27 03:17:48
【问题描述】:

我想要一个 PHP 代码从 MySQL 数据库中的一个表中一个一个地绘制一些 id 而无需随机替换。

现在我是这样实现的:

有一个表叫box

CREATE TABLE box
(
id INT UNSIGNED AUTO_INCREMENT,
PRIMARY KEY (id)
);

假设它包含 15 条记录,

盒子

id:
=====
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
=====

已抽出的id将在下次抽奖时记录并排除。

例如,

一开始绘制的SQL是

SELECT id FROM box RAND() LIMIT 1;`.

说结果是 4。

那我已经画了4,下一个绘制的SQL会是

SELECT id FROM box WHERE id != 4 ORDER BY RAND() LIMIT 1;

这次说结果是6。

也就是我画了4和6,所以接下来的绘制SQL会是

SELECT id FROM box WHERE id != 4 AND id != 6 ORDER BY RAND() LIMIT 1;"

问题是:

如果表'box'包含2000条记录并且我已经绘制了1000+个id,那么下一个用于绘制的SQL会很长。 我想知道更长的 SQL 语句是否会使 MySQL 中的程序或进程运行得更慢? 如果是,是否有 解决方案/设计/算法 来解决这个问题? 表格中的记录可能会增加,所以我无法在绘制之前对ids进行随机排序。


关于问题的更多信息:

PHP代码会被多个用户调用,所以每个用户都有自己绘制的一些不同的id。

实际上,在我当前的设计中,用户绘制的 id 存储在 PHP 会话变量中。当用户想要绘图时,会话变量中存储的 id 将被访问用于绘图 SQL 如上所述。

【问题讨论】:

    标签: php mysql sql algorithm random


    【解决方案1】:

    为什么不在表格中添加另一列来指示该数字是否已被抽出?

    那么你的查询就变成了:

    SELECT id FROM box WHERE drawn = 0 ORDER BY RAND() LIMIT 1

    然后,之后:

    UPDATE box SET drawn = 1 WHERE id = 43 或您绘制的任何 ID。

    【讨论】:

    • 这是因为多个用户会使用代码来绘制id。实际上,已经绘制的 id 是保存在 session 中的。
    • 我不确定我是否理解......为什么有多少用户绘制数字很重要?你是说每个用户每个号码都可以抽一次?在这种情况下,创建一个附加表,将用户映射到为该用户绘制的数字
    【解决方案2】:

    大部分查询的编写时间与您的查询大致相同。

    顺便说一下,您查询中的条件相当于 NOT IN,您可能更容易应用 NOT IN

    SELECT * FROM box WHERE id NOT IN (a[0],a[1]...)
    

    现在关于增加查询大小是否会降低 MySQL 的速度,答案是否定的,不是真的。

    一篇关于为什么它不会放慢速度的文章是here(记住你的查询相当于一个not in)

    【讨论】:

      【解决方案3】:

      您可以为每个用户绘制的数字创建另一个表格

      user_id     id
        1         129
        1         832
        2         23
       ...
      

      然后

      SELECT id FROM box WHERE id NOT IN (SELECT id FROM user_drawn WHERE user_id=1) RAND() LIMIT 1;
      

      【讨论】:

      • 这是另一个类似于我当前的解决方案。您的设计不是将 id 存储在会话变量中,而是将 id 存储在数据库中。但是,您的设计会使此表有许多记录。我期待一个优雅的结构设计来解决这个问题!>
      • 表可以有成千上万条记录。你在用 48k 频谱做什么?
      • 我知道表可以有成千上万条记录。但是记录太多会不会让程序运行很慢呢?
      【解决方案4】:

      如果盒子有固定数量的记录,那么我会倾向于采用用户更多空间但应该运行得更快的方法。由于order by rand(),您的方法需要全表扫描来检索下一个值。

      相反,为每个用户创建一个单独的“盒子”并最初进行随机排序:

      CREATE TABLE userbox (
          userboxid INT UNSIGNED AUTO_INCREMENT,
          int boxid,
          userid int,
          PRIMARY KEY (userboxid),
          KEY (userid, userboxid)
      );
      

      然后,当用户启动时,插入该用户的框:

      insert into userbox
          select id, <userid>
          from box
          order by rand()
      

      随后的检索将使用 select 语句,例如:

      select boxid
      from userbox
      where userboxid > <last user box id> and userid = <userid>
      limit 1
      

      这种方法的优点是,在填充盒子的开销之后,每次检索下一个 id 应该只需要查看索引中的下一个值并从数据页面中获取盒子 id。

      【讨论】:

      • 好设计!但不幸的是,盒子里的记录有时会增加。
      • 框内的更改会影响当前正在访问该框的用户还是仅影响重新开始的新用户?
      猜你喜欢
      • 1970-01-01
      • 2013-07-09
      • 1970-01-01
      • 1970-01-01
      • 2012-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多