【问题标题】:select 1 random row with complex filtering选择 1 个具有复杂过滤的随机行
【发布时间】:2017-04-20 10:05:47
【问题描述】:

我有 2 张桌子:

第一个表users

+-------------------------+---------+------+-----+---------+-------+
| Field                   | Type    | Null | Key | Default | Extra |
+-------------------------+---------+------+-----+---------+-------+
| id                      | int(11) | NO   | PRI | NULL    |       |
| first_name              | text    | NO   |     | NULL    |       |
| age                     | int(11) | YES  |     | NULL    |       |
| settings                | text    | YES  |     | NULL    |       |
+-------------------------+---------+------+-----+---------+-------+

第二张桌子proposals

+---------+---------+------+-----+---------+----------------+
| Field   | Type    | Null | Key | Default | Extra          |
+---------+---------+------+-----+---------+----------------+
| id      | int(11) | NO   | PRI | NULL    | auto_increment |
| from_id | int(11) | NO   |     | NULL    |                |
| to_id   | int(11) | NO   |     | NULL    |                |
| status  | int(11) | NO   |     | NULL    |                |
+---------+---------+------+-----+---------+----------------+

我需要从id 不在to_idproposals 中的用户那里获得1 随机

我正在用这个 sql 做(没有 rand):

SELECT DISTINCT *
FROM profiles
WHERE
    profiles.first_name IS NOT NULL
AND
NOT EXISTS (
    SELECT *
    FROM proposal
    WHERE
        proposal.to_id = profiles.id
)
LIMIT 0 , 1

性能很好:1 row in set (0.00 sec)

但是性能很差:1 row in set (1.78 sec) 当我在末尾添加ORDER BY RAND()

我在users.id 中有大漏洞,我不能使用MAX(id) 之类的东西

我尝试设置随机limit,例如:

...
LIMIT 1234 , 1;
Empty set (2.71 sec)

但这也需要很多时间:(

如何以良好的性能获得users.id 中不存在的proposals.to_id 的随机1 个用户?

我认为我需要先用rand() 获取所有profiles,然后过滤它们,但我不知道该怎么做。

【问题讨论】:

  • RAND() 很难优化。但是here 是一些技巧。大多数其他技术确实涉及表扫描,这是主要的杀手。

标签: mysql sql performance


【解决方案1】:

我有两个问题解决方案。

1) 随机 id,来自https://stackoverflow.com/a/4329447/2051938

SELECT *
FROM profiles AS r1
JOIN
    (SELECT CEIL(RAND() *
                     (SELECT MAX(id)
                        FROM profiles)) AS id)
        AS r2
WHERE
    r1.id >= r2.id
    AND
    r1.first_name IS NOT NULL
AND
NOT EXISTS (
    SELECT *
    FROM proposal
    WHERE
        proposal.to_id = r1.id
)
LIMIT 0 , 1

2) 与ORDER BY RAND()

SELECT *
FROM
    (
        SELECT *
        FROM profiles
        WHERE
            profiles.first_name IS NOT NULL
        ORDER BY RAND()
    ) AS users
WHERE
    NOT EXISTS (
        SELECT *
        FROM proposal
        WHERE
            proposal.to_id = users.id
    )
LIMIT 0 , 1

第一个解决方案更快,但它存在“id 中的漏洞”以及当你从最后得到id 时的问题(用户可能会在匹配之前结束)

第二种方案速度较慢但没有缺陷!

【讨论】:

    【解决方案2】:

    您是否尝试过将not exists 切换为left join

    SELECT DISTINCT *
    FROM   profiles t1
    LEFT JOIN
           proposal t2
    ON     t1.id = t2.to_id
    WHERE  t1.first_name IS NOT NULL AND
           t2.to_id IS NULL
    ORDER BY RAND()
    LIMIT 0 , 1
    

    这将返回profiles 的所有行,对于那些不是matched 的行,它将分配NULL 值,您可以对其进行过滤。

    结果应该是一样的,但性能可能会更好。

    【讨论】:

    • 我错了:#1054 - Unknown column 'profiles.first_name' in 'where clause'
    • 我编辑了我的答案,可能是因为它现在需要使用表别名
    • 你的 sql 比简单的 add "order by rand()" 效率低 :( 结果:1 row in set (2.03 sec)
    【解决方案3】:

    由于RAND() 函数会为结果中存在的每一行分配一个随机数,因此性能将与记录数成正比。

    如果您只想选择一个(随机)记录,您可以申请LIMIT <random number from 0 to record count>, 1

    例如:

    SELECT u.id, count(u.id) as `count`
    FROM users u
    WHERE
        first_name IS NOT NULL
    AND
    NOT EXISTS (
        SELECT *
        FROM proposal
        WHERE
            proposal.to_id = u.id
    )
    LIMIT RAND(0, count-1) , 1
    

    我没有尝试执行这个查询,但是,它MySQL 抱怨在RAND 中使用count,您可以单独计算计数并替换此查询中的值。

    【讨论】:

    • 我有问题:#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '*) as count FROM users u WHERE first_name IS NOT NULL AND NOT EXISTS ' at line 1
    • 试试改成u.id
    • 错误:#1327 - Undeclared variable: RAND,如何解决?
    • 将计数放入变量中(使用另一个查询)并在此查询中使用该值?
    【解决方案4】:

    首先,我认为select distinct 没有必要。因此,请尝试删除它:

    SELECT p.*
    FROM profiles p
    WHERE p.first_name IS NOT NULL AND
          NOT EXISTS (SELECT 1
                      FROM proposal pr
                      WHERE pr.to_id = p.id
                     )
    ORDER BY rand()
    LIMIT 0 , 1;
    

    这可能会有所帮助。那么,减少花费时间的一个相对简单的方法是减少数据量。如果您知道您将始终有数千行满足条件,那么您可以这样做:

    SELECT p.*
    FROM profiles
    WHERE p.first_name IS NOT NULL AND
          NOT EXISTS (SELECT 1
                      FROM proposal pr
                      WHERE pr.to_id = p.id
                     ) AND
          rand() < 0.01
    ORDER BY rand()
    LIMIT 0, 1;
    

    诀窍是找到确保您至少获得一行的比较值。这很棘手,因为您有另一组数据。这是一种使用子查询的方法:

    SELECT p.*
    FROM (SELECT p.*, (@rn := @rn + 1) as rn
          FROM profiles p CROSS JOIN
               (SELECT @rn := 0) params
          WHERE p.first_name IS NOT NULL AND
                NOT EXISTS (SELECT 1
                            FROM proposal pr
                            WHERE pr.to_id = p.id
                           ) 
         ) p
    WHERE rand() < 100 / @rn
    ORDER BY rand()
    LIMIT 0, 1;
    

    这使用一个变量来计算行数,然后随机选择其中的 100 个进行处理。当随机选择 100 行时,至少有一个被选择的可能性非常、非常、非常高。

    这种方法的缺点是子查询需要具体化,这增加了查询的成本。但是,它比对完整数据进行排序要便宜。

    【讨论】:

    • 它需要:1 row in set (1.06 sec) 在 1000 个用户数据库中,这很慢,因为我需要使用 10 000 000 个用户数据库 :(
    猜你喜欢
    • 2021-10-18
    • 1970-01-01
    • 1970-01-01
    • 2021-03-26
    • 2013-11-18
    • 2020-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多