Answers:
如果您想利用LIMIT
性能来提高性能,则需要
LIMIT
之前使用JOIN
如果您可以对这些原则进行精心设计,它们将大有帮助。
我通过观看此YouTube视频(通过法语口音仔细聆听)了解了这些概念
我用这些概念回答了一个非常棘手的StackOverflow问题,该问题涉及从某些表中获取前40名文章:2011年5月12日:从Join Table中获取一行。
在我对该问题的回答(2011年5月16日)中,我编写了以下查询并进行了彻底的测试:
SELECT
AAA.author_id,
AAA.date_created,
IFNULL(BBB.title,'<NO_TITLE>') title,
IFNULL(CCC.filename,'<NO-IMAGE>') filename,
IFNULL(CCC.date_added,'<NO-IMAGE-DATE>') image_date
FROM
(
SELECT
AA.id,
AA.date_added,
BB.author_id,
BB.date_created
FROM
(
SELECT
A.id,IFNULL(MAX(B.date_added),'1900-01-01 00:00:00') date_added
FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A
LEFT JOIN article_images B ON A.id = B.article_id
GROUP BY A.id
) AA
INNER JOIN articles BB USING (id)
) AAA
LEFT JOIN article_contents BBB ON AAA.id=BBB.article_id
LEFT JOIN article_images CCC
ON (AAA.id=CCC.article_id AND AAA.date_added=CCC.date_added)
ORDER BY AAA.date_created DESC;
请注意查询中的行 LIMIT
FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A
该子查询被深埋在三个级别中。这使我可以使用来获取最后40篇文章LIMIT
。然后,我随后执行了必要的JOIN。
LIMIT
里面的子查询不一定是因为指数的基数,数据内容,并从结果集大小的答案LIMIT
。如果您拥有所有“连续的鸭子”(请牢记查询的四个原则),则可以获得令人惊讶的良好结果。LIMIT
仅通过收集键来使查询尽可能简单。(A [LEFT] JOIN B) LIMIT 100
等于(A LIMIT 100) [LEFT] JOIN (B LIMIT 100)
?当[LEFT] JOIN
手段外或内连接
(A LIMIT 100) [LEFT] JOIN B
。这个想法是用来LIMIT
尽早确定结果集的大小。我也使用LEFT JOIN
代替,INNER JOIN
因为LEFT JOIN
会保留左侧键的顺序。
(A LEFT JOIN B) GROUP BY A.pk LIMIT 100
通常可以重写为(A LIMIT 100) LEFT JOIN B GROUP BY A.pk
(此处没有INNER JOIN,对于内部联接,它们将不相等。)Rolando的例子就是这种情况。
执行查询时,首先将其转换为由多个运算符组成的计划。运算符有两种基本类型:阻塞和非阻塞。非阻塞运算符从其子级或子级中为它请求的每一行检索一行(或几行)。另一方面,阻塞运算符必须读入并处理其所有子级的整个行集,然后才能产生任何输出。
排序是典型的阻塞运算符。因此,按顺序进行选择不会从限制中获得很多好处。但是,有些RDBMS可以利用排序算法,该算法需要更少的内存,并且在提供limit子句时更快。在这种情况下,仅存储当前的前n行并将其移出内存就足够了。这可以带来显着的性能提升。但是,我不是100%确信MySQL具有这种能力。
无论哪种方式,即使是极限排序,在产生第一条输出行之前,仍然需要处理整个输入行集。虽然该算法(如果实施)可以加快排序速度,但如果查询的其余部分是最昂贵的部分,则由于提供了限制,总执行时间将不会显着改善。
GROUP BY
可能会导致计划不包含阻塞运算符。
就我而言,即使我(仍然)不明白为什么,我也可以说是。
SELECT g0_.id AS id_0, COUNT(a1_.id_tarifs) AS sclr_1
FROM groupe_jardinerie g0_
INNER JOIN articles_tarifs a1_
ON (a1_.groupe_jardinerie_id = g0_.id)
WHERE g0_.centrale_id = 511
AND a1_.date_fin_tarif >= '2018-01-29 10:46:35'
GROUP BY g0_.id;
(result set)
8 rows in set (**18.14 sec**)
注意时间:18秒。相同的请求,但LIMIT很大:
SELECT g0_.id AS id_0, COUNT(a1_.id_tarifs) AS sclr_1
FROM groupe_jardinerie g0_
INNER JOIN articles_tarifs a1_
ON (a1_.groupe_jardinerie_id = g0_.id)
WHERE g0_.centrale_id = 511
AND a1_.date_fin_tarif >= '2018-01-29 10:46:35'
GROUP BY g0_.id
LIMIT 100000000000;
(exact same result set)
8 rows in set (**1.32 sec**)
快十倍以上!!!
对于两个请求,EXPLAIN给出相同的结果。
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
| 1 | SIMPLE | a1_ | NULL | ALL | IDX_438010BBC10784EF | NULL | NULL | NULL | 795135 | 33.33 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | g0_ | NULL | eq_ref | PRIMARY,IDX_9CA5CF6758A1D71F,IDX_9CA5CF67670C757F | PRIMARY | 4 | phs.a1_.groupe_jardinerie_id | 1 | 50.00 | Using where |
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
LIMIT应该仅会限制结果集(即,如果我执行LIMIT 4,我只会得到上述结果集的前4行)。
LIMIT
。您的第一个查询将在18秒内运行,并给出结果集。由于第一个查询,第二个查询中的所有数据已经缓存在InnoDB缓冲池中,因此,第二个查询当然必须更快,即使重新启动mysql,运行第一个查询,重新启动mysql并运行第二个查询,您将得到相同的结果。。获得更好的结果LIMIT
只能来自以下方面:1)LIMIT
之前JOIN
,2)排序顺序为ASC
或DESC
。
LIMIT
提高效率:优化LIMIT查询