在 MySQL 中,可以使用批量键访问 (BKA) 连接算法,该算法同时使用对连接表的索引访问和连接缓冲区。 BKA 算法支持内连接、外连接和半连接操作,包括嵌套外连接。 BKA 的优势包括由于更高效的表扫描而提高了连接性能。 此外,以前仅用于内连接的块嵌套循环 (BNL) 连接算法已得到扩展,可用于外连接和半连接操作,包括嵌套外连接。
以下部分讨论了作为原始 BNL 算法扩展基础的连接缓冲区管理、扩展的 BNL 算法和 BKA 算法。 有关半连接策略的信息,请参阅 使用半连接转换优化 IN 和 EXISTS 子查询谓词
MySQL 可以使用连接缓冲区来执行以下操作:不仅可以执行无需索引访问内部表的内连接,还可以执行子查询扁平化后出现的外连接和半连接。 此外,当存在对内部表的索引访问时,可以有效地使用连接缓冲区。
连接缓冲区管理代码在存储感兴趣的行列的值时,可以稍微更有效地利用连接缓冲区空间:如果行列的值为 NULL
,则不会为其在缓冲区中分配额外的字节,并且会为 VARCHAR
类型的任何值分配最小字节数。
代码支持两种类型的缓冲区:常规缓冲区和增量缓冲区。 假设连接缓冲区 B1
用于连接表 t1
和 t2
,并且此操作的结果使用连接缓冲区 B2
与表 t3
连接。
常规连接缓冲区包含每个连接操作数的列。 如果
B2
是常规连接缓冲区,则放入B2
的每一行r
都由B1
中的一行r1
的列和表t3
中匹配行r2
的感兴趣列组成。增量连接缓冲区仅包含由第二个连接操作数生成的表的行的列。 也就是说,它是相对于第一个操作数缓冲区中的一行的增量。 如果
B2
是增量连接缓冲区,则它包含行r2
的感兴趣列以及指向B1
中的行r1
的链接。
增量连接缓冲区始终相对于先前连接操作的连接缓冲区是增量的,因此第一个连接操作的缓冲区始终是常规缓冲区。 在刚刚给出的示例中,用于连接表 t1
和 t2
的缓冲区 B1
必须是常规缓冲区。
用于连接操作的增量缓冲区的每一行仅包含要连接的表中的一行的感兴趣列。 这些列通过对由第一个连接操作数生成的表的匹配行的感兴趣列的引用进行扩充。 增量缓冲区中的多行可以引用存储在先前连接缓冲区中的同一行 r
,只要所有这些行都与行 r
匹配即可。
增量缓冲区可以减少从用于先前连接操作的缓冲区复制列的频率。 由于在一般情况下,第一个连接操作数生成的行可以与第二个连接操作数生成的多行匹配,因此这可以节省缓冲区空间。 不需要制作第一个操作数中一行的多个副本。 由于减少了复制时间,增量缓冲区还可以节省处理时间。
系统变量 optimizer_switch
的 block_nested_loop
标志控制哈希连接。
batched_key_access
标志控制优化器如何使用批处理键访问连接算法。
默认情况下,block_nested_loop
为 on
,batched_key_access
为 off
。请参阅 第 10.9.2 节“可切换优化”。也可以应用优化器提示;请参阅 块嵌套循环和批处理键访问算法的优化器提示。
有关半连接策略的信息,请参阅 使用半连接转换优化 IN 和 EXISTS 子查询谓词
MySQL BNL 算法的原始实现已扩展为支持外连接和半连接操作(后来被哈希连接算法取代;请参阅 第 10.2.1.4 节“哈希连接优化”)。
当使用连接缓冲区执行这些操作时,放入缓冲区的每一行都带有一个匹配标志。
如果使用连接缓冲区执行外连接操作,则会针对连接缓冲区中的每一行检查由第二个操作数生成的表的每一行是否匹配。找到匹配项时,将形成一个新的扩展行(原始行加上来自第二个操作数的列),并将其发送以供剩余的连接操作进行进一步扩展。此外,缓冲区中匹配行的匹配标志被启用。在检查完要连接的表的所有行后,将扫描连接缓冲区。缓冲区中每个没有启用匹配标志的行都由 NULL
补码(第二个操作数中每一列的 NULL
值)扩展,并发送以供剩余的连接操作进行进一步扩展。
系统变量 optimizer_switch
的 block_nested_loop
标志控制哈希连接。
有关更多信息,请参阅 第 10.9.2 节“可切换优化”。也可以应用优化器提示;请参阅 块嵌套循环和批处理键访问算法的优化器提示。
在 EXPLAIN
输出中,当 Extra
值包含 Using join buffer (Block Nested Loop)
并且 type
值为 ALL
、index
或 range
时,表示对表使用了 BNL。
有关半连接策略的信息,请参阅 使用半连接转换优化 IN 和 EXISTS 子查询谓词
MySQL 实现了一种称为批处理键访问 (BKA) 连接算法的表连接方法。当对由第二个连接操作数生成的表进行索引访问时,可以应用 BKA。与 BNL 连接算法一样,BKA 连接算法也使用连接缓冲区来累积由连接操作的第一个操作数生成的行的感兴趣列。然后,BKA 算法构建键以访问要连接的表,用于缓冲区中的所有行,并将这些键批量提交给数据库引擎以进行索引查找。键通过多范围读取 (MRR) 接口提交给引擎(请参阅 第 10.2.1.11 节“多范围读取优化”)。提交键后,MRR 引擎函数以最佳方式执行索引查找,获取由这些键找到的连接表的行,并开始向 BKA 连接算法提供匹配的行。每一行匹配行都与连接缓冲区中一行的引用相耦合。
使用 BKA 时,join_buffer_size
的值定义了每次向存储引擎请求时键批处理的大小。缓冲区越大,对连接操作的右侧表进行的顺序访问就越多,这可以显著提高性能。
要使用 BKA,optimizer_switch
系统变量的 batched_key_access
标志必须设置为 on
。BKA 使用 MRR,因此 mrr
标志也必须为 on
。目前,MRR 的成本估算过于悲观。因此,要使用 BKA,mrr_cost_based
也必须为 off
。以下设置启用 BKA
mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
MRR 函数执行有两种情况
第一种情况用于传统的基于磁盘的存储引擎,例如
InnoDB
和MyISAM
。对于这些引擎,通常连接缓冲区中所有行的键都一次性提交给 MRR 接口。特定于引擎的 MRR 函数对提交的键执行索引查找,从中获取行 ID(或主键),然后根据 BKA 算法的请求逐个获取所有这些选定行 ID 的行。返回的每一行都与一个关联引用相关联,该引用允许访问连接缓冲区中的匹配行。MRR 函数以最佳方式获取行:它们按行 ID(主键)顺序获取。这提高了性能,因为读取是按磁盘顺序而不是随机顺序进行的。第二种情况用于远程存储引擎,例如
NDB
。MySQL 服务器(SQL 节点)将连接缓冲区中一部分行的键包及其关联信息发送到 MySQL 集群数据节点。作为回报,SQL 节点接收一个或多个匹配行的包,这些包与相应的关联信息相耦合。BKA 连接算法获取这些行并构建新的连接行。然后,一组新的键被发送到数据节点,并且来自返回包的行被用来构建新的连接行。这个过程一直持续到连接缓冲区中的最后一个键被发送到数据节点,并且 SQL 节点已经接收到并连接了与这些键匹配的所有行。这提高了性能,因为 SQL 节点发送到数据节点的键承载包越少,意味着它与数据节点之间执行连接操作的往返次数越少。
在第一种情况下,连接缓冲区的一部分被保留下来,用于存储由索引查找选择并作为参数传递给 MRR 函数的行 ID(主键)。
没有特殊的缓冲区来存储为连接缓冲区中的行构建的键。相反,一个为缓冲区中的下一行构建键的函数作为参数传递给 MRR 函数。
在 EXPLAIN
输出中,当 Extra
值包含 Using join buffer (Batched Key Access)
并且 type
值为 ref
或 eq_ref
时,表示对表使用了 BKA。
除了使用 optimizer_switch
系统变量在会话范围内控制优化器对 BNL 和 BKA 算法的使用外,MySQL 还支持优化器提示,以便在每个语句的基础上影响优化器。请参阅 第 10.9.3 节“优化器提示”。
要使用 BNL 或 BKA 提示为外连接的任何内表启用连接缓冲,必须为外连接的所有内表启用连接缓冲。