Amazon Aurora MySQL 的最佳实践 - Amazon Aurora

Amazon Aurora MySQL 的最佳实践

本主题提供使用 Amazon Aurora MySQL 数据库集群或向其迁移数据的最佳实践和选项信息。本主题中的信息总结并重申了一些准则和过程,您可以在 管理 Amazon Aurora 数据库集群 中找到这些信息。

确定您连接到的数据库实例

要确定连接到 Aurora MySQL 数据库集群中的哪个数据库实例,请检查 innodb_read_only 全局变量,如以下示例中所示。

SHOW GLOBAL VARIABLES LIKE 'innodb_read_only';

innodb_read_only 变量设置为 ON (如果您已连接到读取器数据库实例)。此设置为 OFF (如果您已连接到写入器数据库实例,例如预置集群中的主实例)。

如果要在应用程序代码中添加逻辑以平衡工作负载或确保写入操作使用正确的连接,该方法可能是非常有用的。

Aurora MySQL 性能和扩展的最佳实践

您可以应用以下最佳实践来改进 Aurora MySQL 集群的性能和可扩展性。

使用 T 实例类进行开发和测试

使用 db.t2db.t3db.t4g 数据库实例类的 Amazon Aurora MySQL 实例最适合不支持长时间运行较高工作负载的应用程序。T 实例旨在提供适度的基准性能,并能够根据您工作负载的需要实现性能的显著突增。它们旨在用于不经常或不持续使用完整 CPU、但偶尔需要突增性能的工作负载。建议仅将 T 数据库实例类用于开发和测试服务器,或其他非生产服务器。有关 T 实例类的更多详细信息,请参阅具爆发能力的实例

如果您的 Aurora 集群大于 40 TB,请勿使用 T 实例类。当数据库带有大量数据时,管理架构对象的内存开销可能会超过 T 实例的容量。

不要在 Amazon Aurora MySQL T 实例上启用 MySQL 性能架构。如果启用了性能架构,T 实例可能会出现内存不足的情况。

提示

如果您的数据库有时处于空闲状态,但有时又有大量工作负载,则可以使用 Aurora Serverless v2 作为 T 实例的替代。使用 Aurora Serverless v2,您可以定义容量范围,Aurora 会根据当前的工作负载自动扩缩数据库。有关使用情况的详细信息,请参阅 使用 Aurora Serverless v2。有关可与 Aurora Serverless v2 一起使用的数据库引擎版本,请参阅 Aurora Serverless v2 的要求和限制

在 Aurora MySQL 数据库集群中使用 T 实例作为数据库实例时,建议执行以下操作:

  • 对数据库集群中的所有实例使用相同的数据库实例类。例如,如果您将 db.t2.medium 用于写入器实例,那么我们建议您也将 db.t2.medium 用于读取器实例。

  • 不要调整任何与内存相关的配置设置,例如 innodb_buffer_pool_size。Aurora 对 T 实例上的内存缓冲区使用一组高度优化的默认值。Aurora 在内存受限的实例上运行时需要采用这些特殊的默认值。如果您在 T 实例上更改任何与内存相关的设置,则更有可能遇到内存不足的情况,即使您的更改旨在增加缓冲区大小。

  • 监控 CPU 积分余额 (CPUCreditBalance) 以确保其处于可持续的水平。也就是说,CPU 积分将在使用 CPU 时按相同的费率累积。

    如果您用完实例的 CPU 积分,则会发现可用 CPU 立即下降,并且实例的读取和写入延迟将会增加。这种情况导致实例的总体性能大大降低。

    如果您的 CPU 积分余额未处于可持续的水平,建议您修改数据库实例以使用支持的 R 数据库实例类之一 (扩展计算)。

    有关监视指标的更多信息,请参阅 在 Amazon RDS 控制台中查看指标

  • 监控写入器实例与读取器实例之间的副本滞后(AuroraReplicaLag)。

    如果读取器实例在写入器实例之前耗尽 CPU 积分,则产生的滞后可能会导致读取器实例频繁重新启动。如果应用程序在读取器实例之间分配较高的读取操作负载,同时写入器实例具有非常低的写入操作负载,则通常会出现这种情况。

    如果您发现副本滞后持续增加,请确保数据库集群中的写入器实例的 CPU 积分余额未被用完。

    如果您的 CPU 积分余额未处于可持续的水平,我们建议您修改数据库实例以使用支持的 R 数据库实例类之一 (扩展计算)。

  • 对于已启用二进制日志记录的数据库集群,将每事务的插入次数保持在 100 万以下。

    如果数据库集群的数据库集群参数组将 binlog_format 参数设置为 OFF 以外的值,并且数据库集群收到的事务包含超过 100 万个要插入的行,数据库集群可能会出现内存不足的情况。您可以监控可释放内存 (FreeableMemory) 指标以确定数据库集群是否用完可用的内存。然后,您可以检查写入操作 (VolumeWriteIOPS) 指标以确定写入器实例是否收到较高的写入操作负载。如果出现这种情况,我们建议您更新应用程序以将事务中的插入数量限制为少于 100 万个。或者,您也可以修改实例以使用支持的 R 数据库实例类之一 (扩展计算)。

使用异步键预取优化 Aurora MySQL 索引的联接查询

Aurora MySQL 可以使用异步键预取(AKP)功能来提高跨索引联接表的查询的性能。该功能通过预测运行查询所需的行 (JOIN 查询需要使用批处理键访问 (BKA) 联接算法和多区间读 (MRR) 优化功能) 来提高性能。有关 BKA 和 MRR 的更多信息,请参阅 MySQL 文档中的块嵌套循环和批处理键访问联接多区间读优化

要利用 AKP 功能,查询必须使用 BKA 和 MRR。通常,当查询的 JOIN 子句使用二级索引并且还需要主索引中的一些列时,会出现此类查询。例如,如果 JOIN 子句表示小型外部表和大型内部表之间的索引值的 equijoin,并且索引在大型表中具有高选择性,则可以使用 AKP。AKP 与 BKA 和 MRR 协作,在计算 JOIN 子句期间执行二级索引到主索引的查找。AKP 标识计算 JOIN 子句期间运行查询所需的行。之后,在运行查询之前,它使用后台线程异步将包含这些行的页加载到内存中。

AKP 适用于 Aurora MySQL 版本 2.10 及更高版本和版本 3。有关 Aurora MySQL 版本的更多信息,请参阅Amazon Aurora MySQL 的数据库引擎更新

启用异步键预取

您可以将 MySQL 服务器变量 aurora_use_key_prefetch 设置为 on 以启用 AKP 功能。默认情况下,该值设置为 on。不过,在您也启用 BKA 联接算法并禁用基于成本的 MRR 功能之前,无法启用 AKP。为此,您必须为 MySQL 服务器变量 optimizer_switch 设置以下值:

  • batched_key_access 设置为 on。该值控制对 BKA 联接算法的使用。默认情况下,该值设置为 off

  • mrr_cost_based 设置为 off。该值控制对基于成本的 MRR 功能的使用。默认情况下,该值设置为 on

目前,您只能在会话级别设置这些值。以下示例说明了如何设置这些值以通过执行 SET 语句来为当前会话启用 AKP。

mysql> set @@session.aurora_use_key_prefetch=on; mysql> set @@session.optimizer_switch='batched_key_access=on,mrr_cost_based=off';

同样,您可以使用 SET 语句为当前会话禁用 AKP 和 BKA 联接算法并重新启用基于成本的 MRR 功能,如以下示例中所示。

mysql> set @@session.aurora_use_key_prefetch=off; mysql> set @@session.optimizer_switch='batched_key_access=off,mrr_cost_based=on';

有关 batched_key_accessmrr_cost_based 优化程序开关的更多信息,请参阅 MySQL 文档中的 可切换的优化

优化异步键预取的查询

您可以确认查询是否能利用 AKP 功能。为此,请使用 EXPLAIN 语句来分析查询,然后再运行查询。EXPLAIN 语句提供有关用于指定查询的执行计划的信息。

EXPLAIN 语句的输出中,Extra 列描述执行计划附带的其他信息。如果 AKP 功能应用于查询中使用的表,则此列包含下列值之一:

  • Using Key Prefetching

  • Using join buffer (Batched Key Access with Key Prefetching)

以下示例说明如何使用 EXPLAIN 来查看可利用 AKP 的查询的执行计划。

mysql> explain select sql_no_cache -> ps_partkey, -> sum(ps_supplycost * ps_availqty) as value -> from -> partsupp, -> supplier, -> nation -> where -> ps_suppkey = s_suppkey -> and s_nationkey = n_nationkey -> and n_name = 'ETHIOPIA' -> group by -> ps_partkey having -> sum(ps_supplycost * ps_availqty) > ( -> select -> sum(ps_supplycost * ps_availqty) * 0.0000003333 -> from -> partsupp, -> supplier, -> nation -> where -> ps_suppkey = s_suppkey -> and s_nationkey = n_nationkey -> and n_name = 'ETHIOPIA' -> ) -> order by -> value desc; +----+-------------+----------+------+-----------------------+---------------+---------+----------------------------------+------+----------+-------------------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------+------+-----------------------+---------------+---------+----------------------------------+------+----------+-------------------------------------------------------------+ | 1 | PRIMARY | nation | ALL | PRIMARY | NULL | NULL | NULL | 25 | 100.00 | Using where; Using temporary; Using filesort | | 1 | PRIMARY | supplier | ref | PRIMARY,i_s_nationkey | i_s_nationkey | 5 | dbt3_scale_10.nation.n_nationkey | 2057 | 100.00 | Using index | | 1 | PRIMARY | partsupp | ref | i_ps_suppkey | i_ps_suppkey | 4 | dbt3_scale_10.supplier.s_suppkey | 42 | 100.00 | Using join buffer (Batched Key Access with Key Prefetching) | | 2 | SUBQUERY | nation | ALL | PRIMARY | NULL | NULL | NULL | 25 | 100.00 | Using where | | 2 | SUBQUERY | supplier | ref | PRIMARY,i_s_nationkey | i_s_nationkey | 5 | dbt3_scale_10.nation.n_nationkey | 2057 | 100.00 | Using index | | 2 | SUBQUERY | partsupp | ref | i_ps_suppkey | i_ps_suppkey | 4 | dbt3_scale_10.supplier.s_suppkey | 42 | 100.00 | Using join buffer (Batched Key Access with Key Prefetching) | +----+-------------+----------+------+-----------------------+---------------+---------+----------------------------------+------+----------+-------------------------------------------------------------+ 6 rows in set, 1 warning (0.00 sec)

有关 EXPLAIN 输出格式的更多信息,请参阅 MySQL 文档中的扩展的 EXPLAIN 输出格式

使用哈希联接优化大型 Aurora MySQL 联接查询

在需要使用 equijoin 联接大量数据时,哈希联接可以提高查询性能。您可以为 Aurora MySQL 启用哈希联接。

哈希联接列可以是任何复杂表达式。在哈希联接列中,您可以使用以下方法比较不同的数据类型:

  • 您可以比较精确数值数据类型类别中的任意类型,例如,intbigintnumericbit

  • 您可以比较近似数值数据类型类别中的任意类型,例如,floatdouble

  • 如果字符串类型具有相同的字符集和排序规则,则可以比较具有这些类型的项目。

  • 如果日期和时间戳数据类型相同,则可以比较具有这些类型的项目。

注意

无法比较不同类别的数据类型。

以下限制适用于 Aurora MySQL 的哈希联接:

  • Aurora MySQL 版本 2 不支持左右外部联接,但版本 3 支持。

  • 不支持半联接(如子查询),除非先具体化子查询。

  • 不支持多表更新或删除。

    注意

    支持单表更新或删除。

  • BLOB 以及空间数据类型列不能是哈希联接中的联接列。

启用哈希联接

启用哈希联接:

  • Aurora MySQL 版本 2 – 将数据库参数或数据库集群参数 aurora_disable_hash_join 设置为 0。关闭 aurora_disable_hash_join 会将 optimizer_switch 的值设置为 hash_join=on

  • Aurora MySQL 版本 3 – 将 MySQL 服务器参数 optimizer_switch 设置为 block_nested_loop=on

哈希联接在 Aurora MySQL 版本 3 中原定设置情况下开启,而在 Aurora MySQL 版本 2 中原定设置情况下关闭。以下示例说明了如何为 Aurora MySQL 版本 3 启用哈希联接。您可以先发布语句 select @@optimizer_switch,以了解 SET 参数字符串中存在的其他设置。更新 optimizer_switch 参数中的一个设置不会删除或修改其他设置。

mysql> SET optimizer_switch='block_nested_loop=on';
注意

对于 Aurora MySQL 版本 3,所有次要版本均支持哈希联接,并且原定设置开启哈希联接。

对于 Aurora MySQL 版本 2,所有次要版本均支持哈希联接。在 Aurora MySQL 版本 2 中,哈希联接功能始终由 aurora_disable_hash_join 值控制。

在使用该设置时,优化程序选择使用基于成本、查询特性和资源可用性的哈希联接。如果成本估算不正确,您可以强制优化程序选择一个哈希联接。为此,请将 MySQL 服务器变量 hash_join_cost_based 设置为 off。以下示例说明了如何强制优化程序选择哈希联接。

mysql> SET optimizer_switch='hash_join_cost_based=off';
注意

此设置将覆盖基于成本的优化程序的决策。虽然该设置对于测试和开发很有用,但我们建议您不要在生产中使用它。

优化哈希联接的查询

要确定查询是否可以使用哈希联接,请先使用 EXPLAIN 语句分析查询。EXPLAIN 语句提供有关用于指定查询的执行计划的信息。

EXPLAIN 语句的输出中,Extra 列描述执行计划附带的其他信息。如果哈希联接应用于查询中使用的表,该列将包含类似下面的值:

  • Using where; Using join buffer (Hash Join Outer table table1_name)

  • Using where; Using join buffer (Hash Join Inner table table2_name)

以下示例说明了如何使用 EXPLAIN 查看哈希联接查询的执行计划。

mysql> explain SELECT sql_no_cache * FROM hj_small, hj_big, hj_big2 -> WHERE hj_small.col1 = hj_big.col1 and hj_big.col1=hj_big2.col1 ORDER BY 1; +----+-------------+----------+------+---------------+------+---------+------+------+----------------------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+------+---------+------+------+----------------------------------------------------------------+ | 1 | SIMPLE | hj_small | ALL | NULL | NULL | NULL | NULL | 6 | Using temporary; Using filesort | | 1 | SIMPLE | hj_big | ALL | NULL | NULL | NULL | NULL | 10 | Using where; Using join buffer (Hash Join Outer table hj_big) | | 1 | SIMPLE | hj_big2 | ALL | NULL | NULL | NULL | NULL | 15 | Using where; Using join buffer (Hash Join Inner table hj_big2) | +----+-------------+----------+------+---------------+------+---------+------+------+----------------------------------------------------------------+ 3 rows in set (0.04 sec)

在输出中,Hash Join Inner table 是用于构建哈希表的表,Hash Join Outer table 是用于探查哈希表的表。

有关扩展的 EXPLAIN 输出格式的更多信息,请参阅 MySQL 产品文档中的扩展的 EXPLAIN 输出格式

在 Aurora MySQL 2.08 及更高版本中,您可以使用 SQL 提示来影响查询是否使用哈希联接,以及用于联接的构建和探查端的表。有关详细信息,请参阅 Aurora MySQL 提示

使用 Amazon Aurora 为 MySQL 数据库扩展读取

您可以将 Amazon Aurora 用于 MySQL 数据库实例,以便利用 Amazon Aurora 的读取扩展功能并为 MySQL 数据库实例扩展读取工作负载。要使用 Aurora 对 MySQL 数据库实例进行读取扩展,请创建 Aurora MySQL 数据库集群并使它成为 MySQL 数据库实例的只读副本。然后连接到 Aurora MySQL 集群以处理读取查询。源数据库可以是 RDS for MySQL 数据库实例或是在 Amazon RDS 外部运行的 MySQL 数据库。有关更多信息,请参阅使用 Amazon Aurora 为 MySQL 数据库扩展读取

优化时间戳操作

当系统变量 time_zone 的值设置为 SYSTEM 时,每个需要时区计算的 MySQL 函数调用都会进行系统库调用。当您运行在高并发条件下返回或更改此类 TIMESTAMP 值的 SQL 语句时,可能会遇到延迟、锁定争用和 CPU 使用率增加的情况。有关更多信息,请参阅 MySQL 文档中的 time_zone

为避免这种行为,我们建议您将 time_zone 数据库集群参数的值更改为 UTC。有关更多信息,请参阅修改数据库集群参数组中的参数

虽然 time_zone 参数是动态的(不需要重新启动数据库服务器),但新值仅用于新连接。为确保更新所有连接以使用新的 time_zone 值,我们建议您在更新数据库集群参数后回收应用程序连接。

Aurora MySQL 高可用性的最佳实践

您可以应用以下最佳实践来改进 Aurora MySQL 集群的可用性。

使用 Amazon Aurora 实现 MySQL 数据库的灾难恢复

您可以在 MySQL 数据库实例中使用 Amazon Aurora 创建异地备份以进行灾难恢复。要使用 Aurora 实现 MySQL 数据库实例的灾难恢复,请创建 Amazon Aurora 数据库集群并使它成为 MySQL 数据库实例的只读副本。这适用于 RDS for MySQL 数据库实例或是在 Amazon RDS 外部运行的 MySQL 数据库。

重要

在 MySQL 数据库实例和 Amazon Aurora MySQL 数据库集群之间设置复制时,应监控复制以确保它正常运行并在必要时进行修复。

有关如何创建 Amazon Aurora MySQL 数据库集群并使它成为 MySQL 数据库实例的只读副本的说明,请遵循 使用 Amazon Aurora 为 MySQL 数据库扩展读取 中的过程。

有关灾难恢复模型的更多信息,请参阅如何为 Amazon Aurora MySQL 集群选择最佳灾难恢复选项

从 MySQL 迁移到 Amazon Aurora MySQL 并减少停机时间

将数据从支持活动应用程序的 MySQL 数据库导入到 Amazon Aurora MySQL 数据库集群时,您可能希望缩短在迁移时发生服务中断的时间。为此,您可以使用 Amazon Relational Database Service 用户指南将数据导入到 MySQL 或 MariaDB 数据库实例并减少停机时间中介绍的过程。如果使用非常大的数据库,该步骤可能是非常有用的。您可以使用该步骤最大限度减少通过网络传送到 AWS 的数据量以降低导入成本。

该过程所列的步骤可将数据库数据的副本传输到 Amazon EC2 实例,并将数据导入到新的 RDS for MySQL 数据库实例。因为 Amazon Aurora 与 MySQL 兼容,所以您可以为目标 Amazon RDS MySQL 数据库实例改用 Amazon Aurora 数据库集群。

避免 Aurora MySQL 数据库实例性能降低、自动重启和故障转移

如果您正在运行繁重的工作负载,或峰值工作负载超出为数据库实例分配的资源,则可能会耗尽运行应用程序和 Aurora 数据库的资源。要获取有关数据库实例的指标,例如 CPU 利用率、内存使用率和使用的数据库连接数,您可以参考 Amazon CloudWatch、Performance Insights 和增强型监控所提供的指标。有关监控数据库实例的更多信息,请参阅 监控 Amazon Aurora 集群中的指标

如果您的工作负载耗尽了您正在使用的资源,则数据库实例可能会变慢、重新启动,甚至故障转移到另一个数据库实例。为避免这种情况,请监控您的资源利用率,检查数据库实例上运行的工作负载,并在必要时进行优化。如果优化不能改善实例指标和缓解资源耗尽问题,请考虑在达到数据库实例的限制之前对其进行纵向扩展。有关可用数据库实例类及其规格的更多信息,请参阅 Aurora 数据库实例类

有关 Aurora MySQL 的建议

以下功能在 Aurora MySQL 中可用于实现 MySQL 兼容性。但是,它们在 Aurora 环境中存在性能、可扩展性、稳定性或兼容性问题。因此,我们建议您在使用这些功能时遵循某些准则。例如,我们不建议使用某些功能进行生产 Aurora 部署。

在 Aurora MySQL 中使用多线程复制

使用多线程二进制日志复制时,SQL 线程会从中继日志中读取事件并将其排队,以便 SQL 工作线程应用。SQL 工作线程由协调器线程管理。尽可能并行应用二进制日志事件。

Aurora MySQL 版本 3 和 Aurora MySQL 2.12.1 及更高版本支持多线程复制。

对于低于 3.04 的 Aurora MySQL 版本,当 Aurora MySQL 数据库集群用作二进制日志复制的只读副本时,Aurora 默认情况下使用单线程复制。

早期的 Aurora MySQL 版本 2 继承了 MySQL Community Edition 中多线程复制方面存在的一些问题。对于这些版本,建议您不要在生产环境中使用多线程复制。

如果您确实要使用多线程复制,建议您对其进行全面测试。

有关在 Amazon Aurora 中使用复制的更多信息,请参阅使用 Amazon Aurora 进行复制。有关 Aurora MySQL 中多线程复制的更多信息,请参阅多线程二进制日志复制

使用本机 MySQL 函数调用 AWS Lambda 函数

我们建议使用原生 MySQL 函数 lambda_synclambda_async 以调用 Lambda 函数。

如果使用已弃用的 mysql.lambda_async 过程,我们建议您将对 mysql.lambda_async 过程的调用封装在一个存储过程中。您可以从不同的来源调用该存储过程,例如,触发器或客户端代码。这种方法可以帮助您避免出现阻抗不一致问题,并使数据库编程人员更轻松地调用 Lambda 函数。

有关从 Amazon Aurora 中调用 Lambda 函数的更多信息,请参阅 从 Amazon Aurora MySQL 数据库集群中调用 Lambda 函数

避免将 XA 事务与 Amazon Aurora MySQL 结合使用

我们建议您不要在 Aurora MySQL 中使用扩展架构 (XA) 事务,因为在 XA 处于 PREPARED 状态时,它们可能会造成较长的恢复时间。如果必须在 Aurora MySQL 中使用 XA 事务,请遵循以下最佳实践:

  • 不要在 PREPARED 状态下打开 XA 事务。

  • 使 XA 事务尽可能小。

有关将 XA 事务与 MySQL 结合使用的更多信息,请参阅 MySQL 文档中的 XA 事务

在 DML 语句执行期间保持打开外键

foreign_key_checks 变量设置为 0 (off) 时,我们强烈建议您不要运行任何数据定义语言 (DDL) 语句。

如果您需要插入或更新暂时违反外键的行,请执行以下步骤:

  1. foreign_key_checks 设置为 0

  2. 执行数据操纵语言 (DML) 更改。

  3. 确保您完成的更改不会违反任何外键约束。

  4. foreign_key_checks 设置为 1 (on)。

此外,请遵循外键约束的这些其他最佳实践:

  • 确保客户端应用程序未将 foreign_key_checks 变量作为 0 变量的一部分设置为 init_connect

  • 如果从诸如 mysqldump 这样的逻辑备份还原失败或还原不完整,请务必先将 foreign_key_checks 设置为 1,然后再在同一会话中开始任何其他操作。当逻辑备份开始时,它会将 foreign_key_checks 设置为 0

配置刷新日志缓冲区的频率

在 MySQL 社区版中,为了使事务持久,必须将 InnoDB 日志缓冲区刷新到持久存储。您可以使用 innodb_flush_log_at_trx_commit 参数来配置将日志缓冲区刷新到磁盘的频率。

在将 innodb_flush_log_at_trx_commit 参数设置为默认值 1 时,每次事务提交时都会刷新日志缓冲区。此设置有助于保持数据库符合 ACID 要求。我们建议您保留原定设置 1。

innodb_flush_log_at_trx_commit 更改为非默认值有助于减少数据操作语言(DML)延迟,但会牺牲日志记录的持久性。这种缺乏持久性使数据库不符合 ACID 要求。我们建议您的数据库符合 ACID 要求,以避免在服务器重新启动时丢失数据的风险。有关此参数的更多信息,请参阅 MySQL 文档中的 innodb_flush_log_at_trx_commit

在 Aurora MySQL 中,重做日志处理被卸载到存储层,因此数据库实例上不会发生刷新到日志文件的过程。发出写入操作时,重做日志将从写入器数据库实例直接发送到 Aurora 集群卷。跨网络执行的仅有的写入操作是写入重做日志记录。始终不会从数据库层写入任何页面。

默认情况下,每个提交事务的线程都要等待来自 Aurora 集群卷的确认。此确认表明该记录和所有先前的重做日志记录均已写入并已达到法定数目。无论是通过自动提交还是显式提交,保留日志记录并达到法定数目都会使事务变得持久。有关 Aurora 存储架构的更多信息,请参阅 Amazon Aurora 存储揭秘

Aurora MySQL 不会像 MySQL 社区版那样将日志刷新到数据文件。但是,在将重做日志记录写入 Aurora 集群卷时,您可以使用 innodb_flush_log_at_trx_commit 参数放松持久性约束。

对于 Aurora MySQL 版本 2:

  • innodb_flush_log_at_trx_commit = 0 或 2 – 数据库不会等待有关重做日志记录已写入 Aurora 集群卷的确认。

  • innodb_flush_log_at_trx_commit = 1 – 数据库会等待有关重做日志记录已写入 Aurora 集群卷的确认。

对于 Aurora MySQL 版本 3:

  • innodb_flush_log_at_trx_commit = 0 – 数据库不会等待有关重做日志记录已写入 Aurora 集群卷的确认。

  • innodb_flush_log_at_trx_commit = 1 或 2 – 数据库会等待有关重做日志记录已写入 Aurora 集群卷的确认。

因此,要在 Aurora MySQL 版本 3 中获得与 Aurora MySQL 版本 2 中将该值设置为 0 或 2 相同的非默认行为,请将此参数设置为 0。

虽然这些设置可以降低客户端的 DML 延迟,但也可能导致在故障转移或重新启动时丢失数据。因此,我们建议您将 innodb_flush_log_at_trx_commit 参数保持设置为默认值 1。

虽然在 MySQL 社区版和 Aurora MySQL 中都可能发生数据丢失,但由于每个数据库的架构不同,对于它们的行为也有所不同。这些架构差异可能导致不同程度的数据丢失。要确保您的数据库符合 ACID 要求,请始终将 innodb_flush_log_at_trx_commit 设置为 1。

注意

在 Aurora MySQL 版本 3 中,在将 innodb_flush_log_at_trx_commit 更改为 1 以外的值之前,必须先将 innodb_trx_commit_allow_data_loss 的值更改为 1。这样做即表示您承认数据丢失的风险。

最大限度地减少 Aurora MySQL 死锁以及排查相关问题

运行工作负载的用户在同时修改同一数据页上的记录时,如果经常遇到对唯一的二级索引或外键违反约束的情形,则可能会遇到更多的死锁和锁等待超时。这些死锁和超时是由于 MySQL 社区版错误修复造成的。

此修复包含在 MySQL 社区版版本 5.7.26 及更高版本中,并已向后移植到 Aurora MySQL 版本 2.10.3 及更高版本中。该修复对于实施可序列性是必要的,其具体方法为:对于这些类型的数据操作语言(DML)操作,针对 InnoDB 表中的记录更改实施额外的锁定。此问题是在调查先前的 MySQL 社区版错误修复所引入的死锁问题时发现的。

该修复更改了对于 InnoDB 存储引擎中元组(行)更新的部分回滚的内部处理。在外键或唯一二级索引上生成违反约束的操作会导致部分回滚。这包括但不限于并发 INSERT...ON DUPLICATE KEY UPDATEREPLACE INTO,INSERT IGNORE 语句(upsert)。

在这种情况下,部分回滚不是指应用程序级事务的回滚,而是当遇到违反约束的情形时,InnoDB 对聚集索引更改进行内部回滚。例如,在 upsert 操作期间发现了重复的键值。

在正常的插入操作中,InnoDB 会自动为每个索引创建聚集和二级索引条目。如果 InnoDB 在 upsert 操作期间在唯一二级索引上检测到重复值,则必须还原聚集索引中插入的条目(部分回滚),然后必须将更新应用于现有的重复行。在此内部部分回滚步骤中,InnoDB 必须锁定被视为操作的一部分的每条记录。该修复通过在部分回滚后引入额外的锁定来确保事务的可序列性。

最大限度地减少 InnoDB 死锁

您可以采用以下方法来减少数据库实例中出现死锁的频率。可以在 MySQL 文档中找到更多示例。

  1. 为了减少出现死锁的几率,请在进行一组相关更改后立即提交事务。可以通过将大型事务(两次提交之间的多行更新)分解为较小的事务来做到这一点。如果您要批量插入行,则请尝试减少批量插入大小,尤其是在使用前面提到的 upsert 操作时。

    要减少可能的部分回滚次数,可以尝试以下一些方法:

    1. 将批量插入操作替换为一次插入一行。这可以减少可能存在冲突的事务持有锁的时间量。

    2. 不使用 REPLACE INTO,而是将 SQL 语句重写为多语句事务,如以下所示:

      BEGIN; DELETE conflicting rows; INSERT new rows; COMMIT;
    3. 不使用 INSERT...ON DUPLICATE KEY UPDATE,而是将 SQL 语句重写为多语句事务,如以下所示:

      BEGIN; SELECT rows that conflict on secondary indexes; UPDATE conflicting rows; INSERT new rows; COMMIT;
  2. 避免长时间运行的事务,无论是活跃的还是空闲的,因为它们可能会被锁定。这包括交互式 MySQL 客户端会话,这些会话可能会在未提交的事务中打开很长一段时间。在优化事务大小或批次大小时,影响可能会有所不同,具体取决于许多因素,例如并发性、重复项数和表结构。任何更改都应根据您的工作负载进行实施和测试。

  3. 在某些情况下,当两个事务尝试以不同的顺序访问一个或多个表中的相同数据集时,可能会出现死锁。为防止这种情况,您可以修改事务以按相同顺序访问数据,从而使访问序列化。例如,创建要完成的事务队列。当多个事务同时发生时,这种方法可以帮助避免死锁。

  4. 向表中添加精心选择的索引可以提高选择性并减少访问行的需求,从而减少锁定。

  5. 如果您遇到间隙锁定,可以将会话或事务的事务隔离级别修改为 READ COMMITTED,以防止出现这种情况。有关 InnoDB 隔离级别及其行为的更多信息,请参阅 MySQL 文档中的事务隔离级别

注意

虽然您可以采取预防措施来降低死锁发生的可能性,但死锁是数据库的预期行为,仍然可能发生。应用程序应具有必要的逻辑,以便在遇到死锁时进行处理。例如,在应用程序中实现重试和退避逻辑。最好解决问题的根本原因,但如果确实出现死锁,应用程序可以选择等待并重试。

监控 InnoDB 死锁

当应用程序事务尝试以导致循环等待的方式获取表级和行级锁定时,MySQL 中可能会发生死锁。偶尔的 InnoDB 死锁不一定是问题,因为 InnoDB 存储引擎会立即检测到这一情况并自动回滚其中一个事务。如果您经常遇到死锁,我们建议您检查和修改您的应用程序,以缓解性能问题并避免死锁。开启死锁检测(原定设置)时,InnoDB 会自动检测事务死锁并回滚一个或多个事务以打破死锁。InnoDB 尝试挑选要回滚的小事务,其中事务的大小由插入、更新或删除的行数决定。

  • SHOW ENGINE 语句 – SHOW ENGINE INNODB STATUS \G 语句包含自上次重新启动以来在数据库上遇到的最新死锁的详细信息

  • MySQL 错误日志 – 如果您经常遇到死锁,其中 SHOW ENGINE 语句的输出不充足,则可以开启 innodb_print_all_deadlocks 数据库集群参数。

    开启此参数后,有关 InnoDB 用户事务中所有死锁的信息将记录在 Aurora MySQL 错误日志中。

  • Amazon CloudWatch 指标 – 我们还建议您使用 CloudWatch 指标 Deadlocks 主动监控死锁。有关更多信息,请参阅Amazon Aurora 的实例级指标

  • Amazon CloudWatch Logs – 利用 CloudWatch Logs,您可以查看指标、分析日志数据并创建实时警报。有关更多信息,请参阅使用 Amazon CloudWatch 监控 Amazon Aurora MySQL 和 Amazon RDS for MySQL 中的错误,并使用 Amazon SNS 发送通知

    使用已开启 innodb_print_all_deadlocks 的 CloudWatch Logs,您可以配置警报,以便在死锁数量超过给定阈值时通知您。要定义阈值,我们建议您观察趋势并使用基于正常工作负载的值。

  • Performance Insights – 当您使用 Performance Insights 时,您可以监控 innodb_deadlocksinnodb_lock_wait_timeout 指标。有关这些指标的更多信息,请参阅 Aurora MySQL 的非本机计数器