MySQL货量有限、流量大的预订数据库查询设计
MySQL query design for booking database with limited amount of goods and heavy traffic
我们是 运行 鲑鱼捕捞许可证预订网站。该站点一年 364 天处理流量都没有问题。第 365 天是许可证销售开始的时候,这就是问题发生的地方。由于流量增加,服务器每年都在挣扎,我们必须进一步优化我们的预订查询。
许可证分为许多不同的类型(tbl_license_types
),每种许可证类型都连接到一个或多个捕鱼区(tbl_zones
)。
每个许可证类型都可以有一个季节性配额,它是 tbl_license_types
中设置为整数字段的单个值。
每个区域都可以有每日配额、每周配额和季节性配额。每日配额每天都是一样的,季节性配额当然是一个单一的值。因此,每日和季节性是 tbl_zones
中的整数字段。但是,每周配额因周而异,因此在单独的 tbl_weekly_quotas
.
中指定
预订可以是一个或多个连续日期,但在 tbl_shopping_cart
(和 tbl_bookings
)中仅表示为 From_date
和 To_date
。对于用户进行的每次预订尝试,都必须根据 tbl_shopping_cart
和 tbl_bookings
.
中已经允许的预订来检查配额
为了能够 count/group 按日期,我们使用名为 view_season_calendar
的视图,其中包含当前季节的所有日期的单个列。
一开始我们使用了一个事务,我们首先进行查询以检查配额,如果配额允许,我们将使用第二个查询将预订插入 tbl_bookings
。
然而,这在相对适中的流量下造成了很多死锁,因此我们将其重新设计为单个查询(伪代码):
INSERT INTO tbl_bookings (_booking_)
WHERE _lowest_found_quota_ >= _requested_number_of_licenses_
其中 _lowest_found_quota_
是一个长约 330 行的 SELECT,具有多个子查询和相关表被多次连接以检查所有配额。
示例:用户想要预订 2020-05-19 至 2020-05-25 区域 5 和 6 的许可证类型 A。
系统需要
- 根据许可证类型 A 季节性配额计算以前的许可证类型 A 预订。
- 根据 5 区每日配额计算 6 个日期中每个日期在 5 区的先前预订。
- 区域 6 的计数相同。
- 根据区域 5 的每周配额,计算日期所属的两周内每个区域 5 的先前预订。
- 区域 6 的计数相同。
- 根据区域 5 季节性配额计算当前季节在区域 5 中的所有先前预订。
- 区域 6 的计数相同。
如果所有都在配额之内,请插入预订。
正如我所说,这在早些时候运行良好,但由于更高的流量负载,我们现在需要进一步优化此查询。我对如何做到这一点有一些想法;
- 在每次预订时使用隔离级别
READ UNCOMMITTED
,直到请求的 zones/license 类型的配额几乎用完,然后回退到默认 REPEATABLE READ
。只要配额还剩很多,计数就不需要 100% 正确。这将大大减少锁等待和死锁,对吧?
- 创建一个或多个视图来记录每个日期、周、区域和许可类型的所有预订,然后在插入的 WHERE 子句中使用这些视图。
- 如果执行 nr 2,请在视图中使用
READ UNCOMMITTED
。如果视图报告相关配额接近满,请取消插入并使用我们今天使用的设计开始一个新的。 (希望流量水平在配额变满之前下降)
对于如何尽可能高效地完成查询的想法,我将不胜感激。
看看能不能在开季前一天开始升级AWS,赶完再降级。这是一个很小的代价,可能会大大提高性能。
与其进行长而复杂的计数查询,不如在进行时递减一些计数器。 (这个 可能有用也可能没有 帮助,所以请尝试这个想法。)
您的网络服务器对其处理的连接数有一些限制;限制它而不是让 2K 用户进入 MySQL 并互相绊倒。想一想当过道拥挤得没人吃完时,一家杂货店是什么样子的!
一定要用"transactions",但不要太宽泛。如果它们包含太多东西,流量将退化为单个文件(and/or 事务将因死锁而中止)。
在事务之外尽可能多地做 -- 例如收集和检查用户 names/addresses 等。如果你这样做 after颁发许可证,准备好在出现问题时撤消许可证。 (这应该在代码中完成,而不是通过 ROLLBACK
.
(更多)
VIEWs
是语法糖;它们不提供任何性能或隔离。 OTOH,如果你 发表了"materialized" 观点,可能会有一些有用的东西。
长 "History list" 是一个潜在的性能问题(尤其是 CPU)。当大量连接同时处于事务中间时可能会发生这种情况——每个连接都需要挂在其数据集的 'snapshot' 上。
尽可能尽快终止交易——即使您转身开始新的交易。数据仓库中的一个示例是在 开始主事务之前执行'normalization' 。 (此示例可能不适用于您的应用。)
考虑让后台任务计算配额。希望常规任务可以 运行 更快,因为它们的事务中没有计算。
一种用于预订行业的技术:(这听起来有点像您的第 1 项。)以最少的锁定向前推进。在最后一刻,以最短的交易时间进行预订并确认房间(飞机座位等)是否仍然可用。
如果整个任务可以分为 (1) 读取一些东西,然后 (2) 写入(并重新读取以验证该东西是否仍然可用),那么...如果读取步骤比写步骤重,添加更多的从站('replicas')并将它们用于读步骤。为写入步骤保留 Master。请注意,复制品很容易(而且便宜)添加并在短时间内扔掉。
每秒速率 = RPS
您的 AWS 参数组要考虑的建议
innodb_lru_scan_depth=100 # from 1024 to conserve 90% of CPU cycles used for function every second
innodb_flush_neighbors=2 # from 1 to reduce time required to lower innodb_buffer_pool_pages_dirty count when busy
read_rnd_buffer_size=128K # from 512K to reduce handler_read_rnd_next RPS of 3,227
innodb_io_capacity=1900 # from 200 to use more of SSD IOPS
log_slow_verbosity=query_plan,explain # to make slow query log more useful
have_symlink=NO # from YES for some protection from ransomware
您会发现这些更改会导致交易更快地完成处理。如需其他帮助,请查看配置文件、联系信息的网络配置文件和免费下载的实用程序脚本以协助性能调整。在我们的 FAQ 页面上,您会发现 "Q. How can I find JOINS or QUERIES not using indexes?" 有助于将 select_scan RPhr 减少到 1,173。 Com_rollback 平均每 2,700 秒 1 次,通常可以通过维护查询中一致的读取顺序进行更正。
我们是 运行 鲑鱼捕捞许可证预订网站。该站点一年 364 天处理流量都没有问题。第 365 天是许可证销售开始的时候,这就是问题发生的地方。由于流量增加,服务器每年都在挣扎,我们必须进一步优化我们的预订查询。
许可证分为许多不同的类型(tbl_license_types
),每种许可证类型都连接到一个或多个捕鱼区(tbl_zones
)。
每个许可证类型都可以有一个季节性配额,它是 tbl_license_types
中设置为整数字段的单个值。
每个区域都可以有每日配额、每周配额和季节性配额。每日配额每天都是一样的,季节性配额当然是一个单一的值。因此,每日和季节性是 tbl_zones
中的整数字段。但是,每周配额因周而异,因此在单独的 tbl_weekly_quotas
.
预订可以是一个或多个连续日期,但在 tbl_shopping_cart
(和 tbl_bookings
)中仅表示为 From_date
和 To_date
。对于用户进行的每次预订尝试,都必须根据 tbl_shopping_cart
和 tbl_bookings
.
为了能够 count/group 按日期,我们使用名为 view_season_calendar
的视图,其中包含当前季节的所有日期的单个列。
一开始我们使用了一个事务,我们首先进行查询以检查配额,如果配额允许,我们将使用第二个查询将预订插入 tbl_bookings
。
然而,这在相对适中的流量下造成了很多死锁,因此我们将其重新设计为单个查询(伪代码):
INSERT INTO tbl_bookings (_booking_)
WHERE _lowest_found_quota_ >= _requested_number_of_licenses_
其中 _lowest_found_quota_
是一个长约 330 行的 SELECT,具有多个子查询和相关表被多次连接以检查所有配额。
示例:用户想要预订 2020-05-19 至 2020-05-25 区域 5 和 6 的许可证类型 A。 系统需要
- 根据许可证类型 A 季节性配额计算以前的许可证类型 A 预订。
- 根据 5 区每日配额计算 6 个日期中每个日期在 5 区的先前预订。
- 区域 6 的计数相同。
- 根据区域 5 的每周配额,计算日期所属的两周内每个区域 5 的先前预订。
- 区域 6 的计数相同。
- 根据区域 5 季节性配额计算当前季节在区域 5 中的所有先前预订。
- 区域 6 的计数相同。
如果所有都在配额之内,请插入预订。
正如我所说,这在早些时候运行良好,但由于更高的流量负载,我们现在需要进一步优化此查询。我对如何做到这一点有一些想法;
- 在每次预订时使用隔离级别
READ UNCOMMITTED
,直到请求的 zones/license 类型的配额几乎用完,然后回退到默认REPEATABLE READ
。只要配额还剩很多,计数就不需要 100% 正确。这将大大减少锁等待和死锁,对吧? - 创建一个或多个视图来记录每个日期、周、区域和许可类型的所有预订,然后在插入的 WHERE 子句中使用这些视图。
- 如果执行 nr 2,请在视图中使用
READ UNCOMMITTED
。如果视图报告相关配额接近满,请取消插入并使用我们今天使用的设计开始一个新的。 (希望流量水平在配额变满之前下降)
对于如何尽可能高效地完成查询的想法,我将不胜感激。
看看能不能在开季前一天开始升级AWS,赶完再降级。这是一个很小的代价,可能会大大提高性能。
与其进行长而复杂的计数查询,不如在进行时递减一些计数器。 (这个 可能有用也可能没有 帮助,所以请尝试这个想法。)
您的网络服务器对其处理的连接数有一些限制;限制它而不是让 2K 用户进入 MySQL 并互相绊倒。想一想当过道拥挤得没人吃完时,一家杂货店是什么样子的!
一定要用"transactions",但不要太宽泛。如果它们包含太多东西,流量将退化为单个文件(and/or 事务将因死锁而中止)。
在事务之外尽可能多地做 -- 例如收集和检查用户 names/addresses 等。如果你这样做 after颁发许可证,准备好在出现问题时撤消许可证。 (这应该在代码中完成,而不是通过
ROLLBACK
.
(更多)
VIEWs
是语法糖;它们不提供任何性能或隔离。 OTOH,如果你 发表了"materialized" 观点,可能会有一些有用的东西。长 "History list" 是一个潜在的性能问题(尤其是 CPU)。当大量连接同时处于事务中间时可能会发生这种情况——每个连接都需要挂在其数据集的 'snapshot' 上。
尽可能尽快终止交易——即使您转身开始新的交易。数据仓库中的一个示例是在 开始主事务之前执行'normalization' 。 (此示例可能不适用于您的应用。)
考虑让后台任务计算配额。希望常规任务可以 运行 更快,因为它们的事务中没有计算。
一种用于预订行业的技术:(这听起来有点像您的第 1 项。)以最少的锁定向前推进。在最后一刻,以最短的交易时间进行预订并确认房间(飞机座位等)是否仍然可用。
如果整个任务可以分为 (1) 读取一些东西,然后 (2) 写入(并重新读取以验证该东西是否仍然可用),那么...如果读取步骤比写步骤重,添加更多的从站('replicas')并将它们用于读步骤。为写入步骤保留 Master。请注意,复制品很容易(而且便宜)添加并在短时间内扔掉。
每秒速率 = RPS
您的 AWS 参数组要考虑的建议
innodb_lru_scan_depth=100 # from 1024 to conserve 90% of CPU cycles used for function every second
innodb_flush_neighbors=2 # from 1 to reduce time required to lower innodb_buffer_pool_pages_dirty count when busy
read_rnd_buffer_size=128K # from 512K to reduce handler_read_rnd_next RPS of 3,227
innodb_io_capacity=1900 # from 200 to use more of SSD IOPS
log_slow_verbosity=query_plan,explain # to make slow query log more useful
have_symlink=NO # from YES for some protection from ransomware
您会发现这些更改会导致交易更快地完成处理。如需其他帮助,请查看配置文件、联系信息的网络配置文件和免费下载的实用程序脚本以协助性能调整。在我们的 FAQ 页面上,您会发现 "Q. How can I find JOINS or QUERIES not using indexes?" 有助于将 select_scan RPhr 减少到 1,173。 Com_rollback 平均每 2,700 秒 1 次,通常可以通过维护查询中一致的读取顺序进行更正。