MySQL 使用嵌套循环算法或其变体来执行表之间的连接。
简单的嵌套循环连接(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)连接算法使用外部循环读取的行缓冲,以减少内部循环中表的读取次数。例如,如果将 10 行读取到缓冲区,并将缓冲区传递给下一个内部循环,那么每个内部循环读取的行都可以与缓冲区中的所有 10 行进行比较。这将大大减少内部表的读取次数。
MySQL 使用哈希连接优化来执行等值连接,当无法使用索引时。
MySQL 连接缓冲具有以下特征:
-
连接缓冲可以在连接类型为
ALL
或index
(即没有可能的键可以使用,分别对数据或索引行进行全扫描)时使用,也可以在range
连接类型时使用。缓冲也适用于外连接,如 第 10.2.1.12 节“块嵌套循环和批量键访问连接” 中所述。 -
只有连接中感兴趣的列被存储在连接缓冲中,而不是整个行。
-
系统变量
join_buffer_size
确定了每个连接缓冲的大小。 -
每个可以缓冲的连接都分配一个缓冲区,因此一个查询可能使用多个连接缓冲。
-
连接缓冲在执行查询之前分配,并在查询完成后释放。
对于之前描述的 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
随着 join_buffer_size
的值增加,表 t3
的扫描次数将减少,直到 join_buffer_size
足够大以容纳所有之前的行组合。在那时,不再通过增加 join_buffer_size
的值来提高速度。