jac*_*ouh 19 php mysql random performance
嗯,这是一个非常古老的问题,从未得到真正的解决方案.我们想要一个表中有3个随机行,大约有30k记录.从MySQL的角度来看,这个表并不是很大,但如果它代表了商店的产品,那么它就具有代表性.例如,当在网页中呈现3个随机产品时,随机选择是有用的.我们想要一个满足以下条件的SQL字符串解决方案:
该表包含以下字段:
CREATE TABLE Products (
ID INT(8) NOT NULL AUTO_INCREMENT,
Name VARCHAR(255) default NULL,
HasImages INT default 0,
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Run Code Online (Sandbox Code Playgroud)
WHERE约束是Products.HasImages = 1,仅允许获取具有可在网页上显示的图像的记录.大约三分之一的记录符合HasImages = 1的条件.
寻求完美,我们首先抛开存在缺陷的现有解决方案:
太慢但在每个查询中保证3个真正随机的记录:
SELECT ID, Name FROM Products WHERE HasImages=1 ORDER BY RAND() LIMIT 3;
Run Code Online (Sandbox Code Playgroud)
*CPU约0.10s,因WHERE子句扫描9690行,使用where; 使用临时; 在Debian Squeeze双核Linux机器上使用filesort,并不是那么糟糕
因为使用临时表和filesort而不能扩展到更大的表,并且在测试Windows7 :: MySQL系统上的第一个查询需要8.52秒.如此糟糕的表现,避免网页不是吗?
从MySQL中选择快速600K行的10个随机行,此处适用仅对单个随机记录有效,因为以下查询会产生几乎总是连续的记录.实际上,它只能在ID中随机获得3个连续记录:
SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT (RAND() * (SELECT MAX(ID) FROM Products)) AS ID)
AS t ON Products.ID >= t.ID
WHERE (Products.HasImages=1)
ORDER BY Products.ID ASC
LIMIT 3;
Run Code Online (Sandbox Code Playgroud)
*CPU约0.01 - 0.19s,随机扫描3200,9690,12000行,但大多数是9690条记录,使用位置.
在MySQL上看到选择bernardo-siu提出的快速600K行的10个随机行:
SELECT Products.ID, Products.Name FROM Products
WHERE ((Products.Hasimages=1) AND RAND() < 16 * 3/30000) LIMIT 3;
Run Code Online (Sandbox Code Playgroud)
*CPU约0.01 - 0.03s,扫描9690行,使用位置.
这里3是所希望的行数,30000是表Products的RecordCount,16是实验系数放大选择以保证3条记录的选择.我不知道因子16在什么基础上是可接受的近似值.
我们在大多数情况下得到3个随机记录并且它非常快,但是没有保证:有时查询只返回2行,有时甚至根本没有记录.
上述三种方法扫描满足WHERE子句的表的所有记录,这里是9690行.
丑陋,但快速和随机。可能会很快变得非常难看,尤其是在下面描述的调整中,所以请确保你真的想要这种方式。
(SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT RAND()*(SELECT MAX(ID) FROM Products) AS ID) AS t ON Products.ID >= t.ID
WHERE Products.HasImages=1
ORDER BY Products.ID
LIMIT 1)
UNION ALL
(SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT RAND()*(SELECT MAX(ID) FROM Products) AS ID) AS t ON Products.ID >= t.ID
WHERE Products.HasImages=1
ORDER BY Products.ID
LIMIT 1)
UNION ALL
(SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT RAND()*(SELECT MAX(ID) FROM Products) AS ID) AS t ON Products.ID >= t.ID
WHERE Products.HasImages=1
ORDER BY Products.ID
LIMIT 1)
Run Code Online (Sandbox Code Playgroud)
如果您的表中的 ID 之间有很大的差距,那么在这些差距之后的行将有更大的机会被此查询获取。在某些情况下,它们会比应有的出现更频繁。这在一般情况下无法解决,但有一个针对常见特殊情况的修复:当 0 和表中第一个现有 ID 之间存在间隙时。
而不是子查询(SELECT RAND()*<max_id> AS ID)使用类似(SELECT <min_id> + RAND()*(<max_id> - <min_id>) AS ID)
如果按原样使用,查询可能会返回重复的行。可以通过使用UNION代替来避免这种情况UNION ALL。这样重复项将被合并,但查询不再保证准确返回 3 行。你也可以解决这个问题,通过获取比你需要的更多的行并像这样限制外部结果:
(SELECT ... LIMIT 1)
UNION (SELECT ... LIMIT 1)
UNION (SELECT ... LIMIT 1)
...
UNION (SELECT ... LIMIT 1)
LIMIT 3
Run Code Online (Sandbox Code Playgroud)
但是,仍然不能保证会提取 3 行。它只会让它更有可能。
| 归档时间: |
|
| 查看次数: |
12209 次 |
| 最近记录: |