MVCC 事务管理器
多版本并发控制(MVCC)事务管理器增强了应用程序的数据库并发管理选项。在 MVCC 模型中,查询数据库时,每个事务看到的都是已提交数据的快照,而不会受到其他任务中正在进行的事务的影响。这保护了事务不会看到由对同一组对象或索引的其他事务更新所导致的不一致数据,从而为每个事务提供了事务隔离。MVCC 管理器允许应用程序在运行时通过设置事务隔离级别来选择事务之间的隔离方式。
隔离级别
当调用事务启动 API 时未指定隔离级别,则事务隔离级别将设置为 MCO_DEFAULT_ISOLATION_LEVEL,默认情况下对于 MVCC 为 MCO_REPEATABLE_READ。
冲突管理
当 MVCC 与 MCO_SERIALIZABLE 以外的隔离级别一起使用时,MCO_READ_WRITE 事务将并发执行。有时并发事务会修改相同的对象,从而产生事务冲突。事务管理器通过中止其中一个冲突事务并允许另一个事务将其更新提交到数据库来解决这些冲突。当事务被中止时,应用程序会收到 MCO_E_CONFLICT 错误代码。应用程序有责任通过类似于以下逻辑来管理这种可能性:
do {
mco_trans_start( db, MCO_READ_WRITE, MCO_TRANS_FOREGROUND, &t);
...<update database>...
rc = mco_trans_commit(t);
} while ( rc == MCO_E_CONFLICT );
请注意,当使用多版本并发控制(MVCC)时,应用程序必须能够容忍由于上述冲突而导致的事务回滚。
如果冲突的数量过高,可能会由于需要重试事务而导致性能急剧下降。出现这种情况时,事务管理器会临时将隔离级别更改为 MCO_SERIALIZABLE。应用程序可以设置乐观控制禁用的冲突阈值。在 C 应用程序中,可以通过调用 mco_trans_optimistic_threshold() 来实现。(此功能仅在 C API 中可用。)
在 C 应用程序中,如果由于事务冲突而被中止的事务百分比超过了 max_conflicts_percent,那么接下来的 disable_period 个连续事务的事务隔离级别将更改为 MCO_SERIALIZABLE。MCO_SERIALIZABLE 允许一次只有一个 MCO_READ_WRITE 事务(消除了冲突的可能性),并且可以与 MCO_READ_ONLY 事务并行运行。默认情况下,乐观阈值设置为 100(这意味着“无论发生多少冲突,都永远不会禁用乐观模式”)。
对象版本与清理
在多版本并发控制(MVCC)中,每次对数据库中的对象进行更改时,系统会生成这些对象的不同版本。这些版本的可见性取决于事务的顺序和当前的隔离级别。当某个版本不再被任何事务需要时,我们会将其删除以节省内存。然而,所有对象版本都包含在为该类定义的索引中,因此排除它们需要重新平衡索引,这可能会导致性能下降。这个清理旧版本的过程被称为垃圾回收。
垃圾回收策略
垃圾回收(GC)策略可以在运行时通过 C API mco_trans_set_gc_policy() 进行设置。您可以选择以下几种策略:
- - MCO_GC_DISABLED:禁用垃圾回收
- - MCO_GC_SELF_VERSIONS:每个会话仅清理其自身的垃圾。当每个会话(连接)处理其自身的类(表)子集时,此策略可能更高效
- - MCO_GC_ON_MODIFICATION:任何会话中的读写事务都会触发所有会话(连接)的垃圾回收
- - MCO_GC_ALWAYS:与 MCO_GC_ON_MODIFICATION 类似,但只读事务也会触发垃圾回收
默认情况下,系统使用 MCO_GC_ON_MODIFICATION 策略,这通常是最佳实践。这意味着,对于每个 MCO_READ_WRITE 事务,SmartEDB 运行时会在事务完成时立即尝试清理“垃圾”。但如果存在长时间运行的事务,则旧对象版本将不会在很长一段时间内被移除。在这种情况下,频繁的垃圾回收可能会浪费时间和 CPU 资源。因此,我们建议启动一个或多个单独的垃圾回收线程,这可能是最优的选择。(在以服务器模式启动 xSQL 时,您可以在配置文件中指定垃圾回收线程的数量。)
考虑一种常见的情况:如果您的应用程序中有大量只读(MCO_READ_ONLY)事务,而更新(MCO_READ_WRITE)事务较少,更新事务将无法执行清理操作,因为旧版本仍可能被只读事务访问,而只读事务由于 MCO_GC_ON_MODIFICATION 策略也不会执行清理操作。这意味着所有版本都将一直保留,直到下一批更新,这会导致性能下降。在这种情况下,使用 MCO_GC_ALWAYS 策略可能更为合适。
MCO_GC_DISABLED 策略旨在用于将垃圾回收限制在专用会话(连接)中。在这种情况下,工作会话将采用此策略,而具有 MCO_GC_ALWAYS 策略的专用会话(或多个会话)将在单独的线程中进行垃圾回收。
持久数据库的特殊情况
当一个或多个 GC 线程使用策略 MCO_GC_ALWAYS 时,对于持久(磁盘)对象存在一种特殊情况。如果一个 MCO_READ_ONLY 事务尝试执行 GC 并删除某些版本,则实际上它会变成一个 MCO_READ_WRITE 事务,并需要写入事务日志。这就是为什么默认策略是 MCO_GC_ON_MODIFICATION 的原因;这样 MCO_READ_ONLY 事务就不会尝试执行 GC。
回收未使用的数据存储空间
当使用 MVCC 事务管理器时,在发生崩溃的情况下,持久数据库可能包含未删除的旧版本和工作副本。虽然它们的存在不会破坏数据库的一致性,也不会妨碍应用程序的正常工作,但会不必要地消耗空间。检测这些过时的对象版本需要对数据库进行完整扫描。因此,恢复过程不会自动执行此功能。相反,在 C/C++ 应用程序中,您可以通过调用 mco_disk_database_vacuum() 函数显式地删除未使用的版本。
MCO_RET mco_disk_database_vacuum(mco_db_h con);
请注意,mco_disk_database_vacuum() 函数需要对数据库进行独占访问,因此在真空操作完成且该函数将控制权交还给应用程序之前,无法对数据库执行任何操作。
或者,应用程序可以在调用 mco_db_open_dev() 时通过在 mco_db_params_t 中设置 MCO_DB_MODE_MVCC_AUTO_VACUUM 模式掩码来启用修复过程。