Aurora MySQL 隔离级别
了解 Aurora MySQL 集群中的数据库实例如何实现隔离的数据库属性。本主题说明 Aurora MySQL 默认行为如何在严格一致性和高性能之间取得平衡。您可以根据工作负载的特性,使用这些信息帮助您决定何时更改默认。
写入器实例的可用隔离级别
您可以在 Aurora MySQL 数据库集群的主实例上使用隔离级别 REPEATABLE READ
、READ COMMITTED
、READ UNCOMMITTED
和 SERIALIZABLE
。这些隔离级别在 Aurora MySQL 中的工作方式与在 RDS for MySQL 中的工作方式相同。
读取器实例的 REPEATABLE READ 隔离级别
默认情况下,配置为只读 Aurora 副本的 Aurora MySQL 数据库实例始终使用 REPEATABLE
READ
隔离级别。这些数据库实例会忽略任何 SET TRANSACTION ISOLATION LEVEL
语句并继续使用 REPEATABLE READ
隔离级别。
您无法使用数据库参数或数据库集群参数为读取器数据库实例设置隔离级别。
读取器实例的 READ COMMITTED 隔离级别
如果您的应用程序包括主实例上的写入密集型工作负载和 Aurora 副本上的长时间运行的查询,则可能会产生大量的清除滞后。当内部垃圾回收被长时间运行的查询阻止时,就会发生清除滞后。您看到的症状是 SHOW ENGINE INNODB STATUS
命令输出中的 history list length
值很高。可以使用 CloudWatch 中的 RollbackSegmentHistoryListLength
指标监控该值。大量的清除滞后可能会降低二级索引的效用,降低整体查询性能并导致浪费存储空间。
如果遇到此类问题,可以设置 Aurora MySQL 会话级别配置设置 aurora_read_replica_read_committed
,以在 Aurora 副本上使用 READ COMMITTED
隔离级别。应用此设置时,可以帮助减少在修改表的事务的同时执行长时间运行的查询可能导致的速度下降和空间浪费情况。
建议您在使用此设置之前一定要了解 READ COMMITTED
隔离的特定 Aurora MySQL 行为。Aurora 副本 READ COMMITTED
行为符合 ANSI SQL 标准。但是,隔离没有您可能熟悉的典型 MySQL READ COMMITTED
行为那么严格。因此,Aurora MySQL 只读副本上 READ COMMITTED
之下的查询结果与您可能看到的 Aurora MySQL 主实例或 RDS for MySQL 上 READ COMMITTED
之下的同一查询的结果可能不同。您可以考虑将 aurora_read_replica_read_committed
设置用于诸如扫描超大型数据库的综合报告之类的情况。相比之下,在精度和可重复性很重要的小型结果集的短查询中,您可能会避免使用它。
READ COMMITTED
隔离级别不适用于 Aurora 全局数据库的辅助集群中使用写入转发特征的会话。有关写入转发的信息,请参阅 在 Amazon Aurora Global Database 中使用写入转发。
对读取器使用 READ COMMITTED
要对 Aurora 副本使用 READ COMMITTED
隔离级别,请将 aurora_read_replica_read_committed
配置设置设为 ON
。在连接到特定的 Aurora 副本时,在会话级别使用此设置。为此,请运行以下 SQL 命令。
set session aurora_read_replica_read_committed = ON; set session transaction isolation level read committed;
您可能会临时使用此配置设置,以执行交互式、一次性查询。您可能还想运行一个从 READ COMMITTED
隔离级别中受益的报告或数据分析应用程序,而其他应用程序的默认保持不变。
开启 aurora_read_replica_read_committed
设置后,使用 SET TRANSACTION ISOLATION
LEVEL
命令为适当的事务指定隔离级别。
set transaction isolation level read committed;
Aurora 副本上的 READ COMMITTED 行为差异
aurora_read_replica_read_committed
设置使 READ COMMITTED
隔离级别可用于 Aurora 副本,并具有针对长时间运行的事务进行优化的一致性行为。Aurora 副本上的 READ
COMMITTED
隔离级别没有 Aurora 主实例上的隔离那么严格。因此,仅在您知道查询可接受某些类型不一致结果的可能性的 Aurora 副本上启用此设置。
当 aurora_read_replica_read_committed
设置打开时,您的查询可能会遇到某些类型的读取异常。理解并处理应用程序代码中的两种异常特别重要。在查询运行期间提交另一个事务时,将发生不可重复的读取。长时间运行的查询在查询开始时看到的数据可能与在结束时看到的数据不同。当其他事务导致在查询运行期间将对现有行进行重组,并且查询将两次读取一行或多行时,将发生幻读。
您的查询可能会因幻读而导致行数不一致;也可能由于不可重复的读取而返回不完整或不一致的结果。例如,假设联接操作引用由 SQL 语句并发修改的表,如 INSERT
或 DELETE
。在这种情况下,联接查询可能从一个表读取一行,但不从另一个表读取对应的行。
ANSI SQL 标准允许 READ COMMITTED
隔离级别存在这两种行为。但是,这些行为与 READ COMMITTED
的典型 MySQL 实现不同。因此,启用 aurora_read_replica_read_committed
设置之前,请先检查任何现有的 SQL 代码,以验证其在更宽松的一致性模型下是否按预期运行。
启用此设置时,READ COMMITTED
隔离级别下的行数和其他结果可能不具有强一致性。因此,通常只在运行聚合大量数据且无需绝对精度的分析查询时才启用该设置。如果没有这些类型的长时间运行的查询以及写入密集型工作负载,则可能不需要 aurora_read_replica_read_committed
设置。如果没有长时间运行的查询和写入密集型工作负载的组合,就不太可能遇到历史记录列表长度的问题。
例 显示 READ COMMITTED 针对 Aurora 副本的隔离行为的查询
以下示例展示了如果事务同时修改关联表,针对 Aurora 副本的 READ COMMITTED
查询如何返回不可重复的结果。表 BIG_TABLE
在任何查询开始之前包含 100 万行。其他数据操作语言(DML)语句在运行时会添加、删除或更改行。
READ COMMITTED
隔离级别下针对 Aurora 主实例的查询生成可预测的结果。但是,在每个长时间运行的查询的生命周期内保持一致的读取视图,这样所产生的开销可能会导致以后的垃圾回收成本高昂。
我们优化了 READ COMMITTED
隔离级别下对 Aurora 副本的查询,以最大程度减少这种垃圾回收开销。权衡之下,结果可能有所不同,具体取决于查询是否检索在查询运行期间提交的事务所添加、删除或重组的行。允许查询考虑这些行,但不要求这样做。出于演示目的,查询仅使用 COUNT(*)
函数检查表中的行数。
Time | Aurora 主实例上的 DML 语句 | 针对 Aurora 主实例(具有 READ COMMITTED)的查询 | 针对 Aurora 副本(具有 READ COMMITTED)的查询 |
---|---|---|---|
T1 |
INSERT INTO big_table SELECT * FROM other_table LIMIT 1000000; COMMIT;
|
||
T2 | Q1:SELECT COUNT(*) FROM big_table; |
Q2:SELECT COUNT(*) FROM big_table; |
|
T3 |
INSERT INTO big_table (c1, c2) VALUES (1, 'one more row'); COMMIT;
|
||
T4 | 如果 Q1 现在完成,则结果为 1,000,000。 | 如果 Q2 现在完成,则结果为 1,000,000 或 1,000,001。 | |
T5 |
DELETE FROM big_table LIMIT 2; COMMIT;
|
||
T6 | 如果 Q1 现在完成,则结果为 1,000,000。 | 如果 Q2 现在完成,则结果为 1,000,000 或 1,000,001 或 999,999 或 999,998。 | |
T7 |
UPDATE big_table SET c2 = CONCAT(c2,c2,c2); COMMIT;
|
||
T8 | 如果 Q1 现在完成,则结果为 1,000,000。 | 如果 Q2 现在完成,则结果为 1,000,000 或 1,000,001 或 999,999 或可能某个更大的数字。 | |
T9 | Q3:SELECT COUNT(*) FROM big_table; |
Q4:SELECT COUNT(*) FROM big_table; |
|
T10 | 如果 Q3 现在完成,则结果为 999,999。 | 如果 Q4 现在完成,则结果为 999,999。 | |
T11 | Q5:SELECT COUNT(*) FROM parent_table p JOIN child_table c ON (p.id = c.id) WHERE p.id = 1000; |
Q6:SELECT COUNT(*) FROM parent_table p JOIN child_table c ON (p.id = c.id) WHERE p.id = 1000; |
|
T12 |
INSERT INTO parent_table (id, s) VALUES (1000, 'hello'); INSERT INTO child_table (id, s) VALUES
(1000, 'world'); COMMIT;
|
||
T13 | 如果 Q5 现在完成,则结果为 0。 | 如果 Q6 现在完成,则结果为 0 或 1。 |
如果查询快速完成,则在任何其他事务执行 DML 语句并提交之前,结果是可预测的,并且主实例与 Aurora 副本之间也是如此。让我们从第一个查询开始详细研究行为的差异。
Q1 的结果高度可预测,因为主实例上的 READ COMMITTED
使用类似于 REPEATABLE READ
隔离级别的强一致性模型。
Q2 的结果可能会因在该查询运行期间提交的事务而异。例如,假设其他事务执行 DML 语句并在查询运行期间提交。在这种情况下,针对具有 READ COMMITTED
隔离级别的 Aurora 副本的查询可能考虑更改,也可能不考虑更改。不能像在 REPEATABLE READ
隔离级别下那样预测行计数。它们也不像针对 READ COMMITTED
隔离级别下的主实例或针对 RDS for MySQL 实例运行的查询那样可预测。
T7 处的 UPDATE
语句实际上并未更改表中的行数。但是,通过更改可变长度列的长度,该语句可能导致在内部对行进行重组。长时间运行的 READ COMMITTED
事务可能会看到某行的旧版本,随后又在同一查询中看到该行的新版本。查询还可以跳过该行的旧版本和新版本,因此行计数可能与预期的不同。
Q5 和 Q6 的结果可能相同,也可能略有不同。READ
COMMITTED
下针对 Aurora 副本的查询 Q6 可以查看(但不是必须查看)查询运行期间提交的新行。它也可能从一个表中看到该行,但从另一表中看不到该行。如果联接查询在两个表中均未找到匹配的行,则返回的计数为零。如果查询的确在 PARENT_TABLE
和 CHILD_TABLE
中找到了新行,则该查询返回的计数为一。在长时间运行的查询中,从联接的表进行查找的时间可能相隔很远。
注意
这些行为上的差异取决于事务何时提交以及查询何时处理底层表行。因此,在耗时数分钟或数小时的报告查询以及同时在处理 OLTP 事务的 Aurora 集群上运行的报表查询中,您最有可能看到这样的差异。这些类型的混合工作负载从 Aurora 副本上的 READ COMMITTED
隔离级别获益最大。