原文地址:https://dev.mysql.com/doc/refman/5.7/en/nested-loop-joins.html
译文:
8.2.1.6 嵌套循环连接算法
MySQL使用嵌套循环算法或其变体执行表之间的连接。
嵌套循环连接(NLJ)算法
一个简单的nested-loop join (NLJ)算法每次读取一个循环中的第一个表中的行,将每一行传递给一个嵌套循环,该循环处理连接中的下一个表。只要还有表需要连接,这个过程就会重复多次。
假设三个表t1、t2和t3之间的连接将使用以下连接类型执行:
Table Join Type
t1 range
t2 ref
t3 ALL
如果使用了NLJ算法,连接会被处理成如下形式:
for each row in t1 matching range {
for each row in t2 matching reference key {
for each row in t3 {
if row satisfies join conditions, send to client
}
}
}
由于NLJ算法每次将一行从外部循环传递到内部循环,因此它通常会多次读取在内部循环中处理的表。
块嵌套循环连接(BNL)算法
BNL算法通过使用外部循环中读取行的缓冲,来减少内部循环中的表必须被读取的次数。例如,如果将10行读入缓冲区,并将缓冲区传递给下一个内部循环,则可以将内部循环中读取的每一行与缓冲区中的所有10行进行比较。这样可以将必须读取内部表的次数减少一个数量级。
MySQL连接缓冲具有如下特性:
1)当连接类型为all或index(换句话说,当不能使用可能的索引键时,将分别对数据行或索引行进行全面扫描)或range时,可以使用连接缓冲。缓冲区的使用同样适用于外链接,可以参考:Section 8.2.1.11, “Block Nested-Loop and Batched Key Access Joins”;
2)永远不会为第一个非常量表分配连接缓冲区,即使连接是all类型或index类型;
3)只有与联接相关的列存储在联接缓冲区中,而不是所有的行;
4)系统变量join_buffer_size决定了每个用于处理查询的连接缓冲区的大小;
5)每个可以缓冲的连接都会被分配一个缓冲区,所以可能使用多个连接缓冲区处理给定的查询;
6)连接缓冲区在执行连接之前分配,在查询完成后释放。
例如对于前文使用NLJ算法(没有缓冲区)的连接,使用缓冲后连接被处理成如下形式:
for each row in t1 matching range {
for each row in t2 matching reference key {
store used columns from t1, t2 in join buffer
if buffer is full {
for each row in t3 {
for each t1, t2 combination in join buffer {
if row satisfies join conditions, send to client
}
}
empty join buffer
}
}
}
if buffer is not empty {
for each row in t3 {
for each t1, t2 combination in join buffer {
if row satisfies join conditions, send to client
}
}
}
如果S是在存储在连接缓冲区中的t1和t2组合的大小,C为缓冲区中组合的个数,则扫描表t3的次数为:
(S * C)/join_buffer_size + 1
t3表扫描的次数随着join_buffer_size的值的增加而减少,直到join_buffer_size足够大以容纳以前所有的行组合时为止。在这种情况下,使join_buffer_size变大并不能提高速度。
PS:由于水平有限,译文中难免存在谬误,欢迎批评指正。