注:本文翻译自https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-intro-about_mvcc.html
Greenplum数据库使用PostgreSQL多版本并发控制(MVCC)模型来管理堆表的并发事务。
数据库管理系统中的并发控制允许并发查询以正确的结果完成,同时确保数据库的完整性。传统数据库使用两阶段锁定协议,防止事务修改已被另一个并发事务读取的数据,并防止任何并发事务读取或写入另一个事务已更新的数据。协调事务所需的锁会给数据库增加争用,从而降低整体事务吞吐量。
Greenplum数据库使用PostgreSQL多版本并发控制(MVCC)模型来管理堆表的并发性。使用MVCC,每个查询在查询开始时都对数据库的快照进行操作。查询运行时,无法看到其他并发事务所做的更改。这可确保查询看到数据库的一致视图。读取行的查询永远不会阻塞等待写行的事务。相反,写行的查询不会被读行的事务阻塞。与使用锁来协调读写数据的事务之间的访问的传统数据库系统相比,这允许更高的并发性。
注:使用不同的并发控制模型管理追加优化的表。它们适用于从不或很少执行行级更新的"一次写,多次读"应用程序。
快照
MVCC模型依赖于系统管理多个版本数据行的能力。查询在查询开始时对数据库的快照进行操作。快照是在语句或事务开头可见的一组行。快照确保查询在执行期间具有一致且有效的数据库视图。
每个事务被分配一个唯一的事务ID (XID),一个递增的32位值。当一个新事务启动时,它被分配下一个XID。未包含在事务中的SQL语句被视为单语句事务---隐式地添加BEGIN和COMMIT。这类似于某些数据库系统中的自动提交。
注:Greenplum Database仅将XID值分配给涉及DDL或DML操作的事务,这些事务通常是唯一需要XID的事务。
当事务插入一行时,XID与该行一起保存在xmin系统列中。当事务删除一行时,XID保存在xmax系统列中。更新一行被视为删除和插入,因此XID被保存为当前行的xmax和新插入行的xmin。xmin和xmax列以及事务完成状态指定了一个事务范围,其中行版本是可见的。一个事务可以看到所有小于xmin的事务的效果,这些事务保证被提交,但是它不能看到大于或等于xmax的任何事务的效果。
多语句事务还必须记录事务中的哪个命令插入了一行(cmin)或删除了一行(cmax),以便事务可以看到事务中先前命令所做的更改。命令序列仅在事务期间相关,因此在事务开始时将该序列重置为0。
XID是数据库的一个属性。每个段数据库都有自己的XID序列,不能与其他段数据库的XID进行比较。协调器使用一个称为gp_session_id的集群范围的会话ID号来协调分布式事务。段维护分布式事务id与其本地xid的映射。协调器使用两阶段提交协议协调所有段中的分布式事务。如果事务在任何一个段上失败,它将在所有段上回滚。
使用SELECT语句可以看到任意一行的xmin, xmax, cmin和cmax列:
bash
SELECT xmin, xmax, cmin, cmax, * FROM <tablename>;
因为您在协调器上运行SELECT命令,所以xid是分布式事务id。如果可以在单个段数据库中运行该命令,则xmin和xmax值将是该段的本地xid。
注:Greenplum Database将复制表的所有行分发到每个段,因此每个段上的每一行都是重复的。每个段实例为系统列xmin、xmax、cmin和cmax以及gp_segment_id和ctid维护自己的值。Greenplum数据库不允许用户查询访问复制表的这些系统列,因为它们没有一个明确的值可供查询。
事务ID回卷
MVCC模型使用事务id (xid)来确定哪些行在查询或事务的开头是可见的。XID是一个32位值,因此在该值溢出并归零之前,数据库理论上可以运行超过40亿个事务。然而,Greenplum数据库对xid使用模232算法,它允许事务id进行回卷装,就像时钟在12点钟回卷一样。对于任何给定的XID,可能有大约20亿个过去的XID和20亿个未来的XID。这种方法一直有效,直到一行的某个版本持续了大约20亿个事务,这时它突然出现为一个新行。为了防止这种情况,Greenplum有一个特殊的XID,称为FrozenXID,它总是被认为比与之比较的任何常规XID更老。在20亿个事务中,一行的xmin必须替换为FrozenXID,这是VACUUM命令执行的功能之一。
至少每20亿个事务清理一次数据库可以防止XID回卷。Greenplum数据库监视事务ID,并在需要VACUUM操作时发出警告。
当事务ID的很大一部分不再可用并且在事务ID环绕发生之前发出警告:
bash
WARNING: database "<database_name>" must be vacuumed within <number_of_transactions> transactions
当发出警告时,需要进行VACUUM操作。如果没有执行VACUUM操作,Greenplum数据库将停止创建事务,以避免在事务ID环绕发生之前达到限制并发出此错误时可能出现的数据丢失:
bash
FATAL: database is not accepting commands to avoid wraparound data loss in database "<database_name>"
当出现警告和错误时,服务器配置参数xid_warn_limit和xid_stop_limit控制。xid_warn_limit参数指定发出警告时在xid_stop_limit之前的事务id的数量。xid_stop_limit参数指定了当发出错误且无法创建新事务时,在发生环绕之前的事务id数。
事务隔离级别
SQL标准定义了事务隔离的四个级别。最严格的是Serializable,该标准将其定义为任何一组Serializable事务的并发执行都保证产生与以某种顺序一次运行一个事务相同的效果。其他三个级别是根据并发事务之间的交互产生的现象来定义的,这种交互不能在每个级别上发生。该标准指出,由于Serializable的定义,在该级别上不可能出现这些现象。
各级禁止的现象有:
- 脏读------事务读取并发未提交事务写入的数据。
- 不可重复读取------事务重新读取它以前读过的数据,并发现数据已被另一个事务(自初始读取以来提交的事务)修改。
- 幻读---事务重新执行查询,返回满足搜索条件的行集,并发现满足条件的行集由于最近提交的另一个事务而发生了更改。
- 序列化异常------成功提交一组事务的结果与每次运行这些事务的所有可能顺序不一致。
Greenplum数据库只实现两个不同的事务隔离级别,尽管您可以请求所描述的四个级别中的任何一个。Greenplum数据库READ UNCOMMITTED级别的行为类似于READ COMMITTED,而SERIALIZABLE级别则回落到REPEATABLE READ。
该表还显示了Greenplum数据库的REPEATABLE READ实现不允许幻读。这在SQL标准下是可以接受的,因为标准规定了在某些隔离级别上不能发生哪些异常;更高的保证是可以接受的。
重要:一些Greenplum数据库数据类型和函数有关于事务行为的特殊规则。特别是,对序列所做的更改(因此使用serial声明的列的计数器)对所有其他事务都是立即可见的,并且如果进行更改的事务终止,则不会回滚。
设置隔离级别
Greenplum数据库的默认事务隔离级别由default_transaction_isolation服务器配置参数指定,初始值为READ COMMITTED。
当您在会话中设置default_transaction_isolation时,您为会话中的所有事务指定默认事务隔离级别。
要设置当前事务的隔离级别,可以使用set transaction SQL命令。请确保在任何SELECT、INSERT、DELETE、UPDATE或COPY语句之前设置隔离级别:
bash
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
...
COMMIT;
也可以在BEGIN语句中指定隔离级别:
bash
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
从表中移除过期行
更新或删除一行会在表中留下该行的过期版本。当过期的行不再被任何活动事务引用时,可以删除它,并且可以重用它所占用的空间。VACUUM命令标记过期行所使用的空间以供重用。
当过期的行在表中积累时,必须扩展磁盘文件以容纳新行。由于运行查询所需的磁盘I/O增加,性能会下降。这种情况被称为膨胀,应该通过定期吸尘来处理。
VACUUM命令(不带FULL)可以与其他查询并发运行。它将过期行的先前使用的空间标记为空闲空间,并更新空闲空间映射。当Greenplum Database稍后需要为新行提供空间时,它首先查询表的空闲空间映射以查找具有可用空间的页面。如果没有找到,则将新页追加到文件中。
VACUUM(没有FULL)不会合并页或减小磁盘上表的大小。它恢复的空间只能通过空闲空间图使用。为了防止磁盘文件增长,经常运行VACUUM是很重要的。所需的VACUUM运行频率取决于表中更新和删除的频率(插入只添加新行)。频繁更新的表可能需要每天运行几次VACUUM,以确保可以通过空闲空间映射找到可用的空闲空间。在运行更新或删除大量行的事务之后运行VACUUM也很重要。
VACUUM FULL命令重写没有过期行的表,将表减小到最小大小。检查表中的每一页,并将可见行上移到尚未完全打包的页中。空页将被丢弃。表被锁定,直到VACUUM FULL完成。与常规的VACUUM命令相比,这是非常昂贵的,可以通过定期vacuum来避免或推迟。最好在维护期间运行VACUUM FULL。VACUUM FULL的另一种替代方法是使用CREATE table AS语句重新创建表,然后删除旧表。
您可以运行VACUUM VERBOSE tablename以按段获取报告,其中包含已删除的死行数、受影响的页面数以及具有可用空闲空间的页面数。
查询pg_class系统表,了解表在所有段上使用了多少页。确保首先对表进行ANALYZE以获得准确的数据。
bash
SELECT relname, relpages, reltuples FROM pg_class WHERE relname='<tablename>';
另一个有用的工具是gp_toolkit模式中的gp_bloat_diag视图,它通过比较表使用的实际页数与预期页数来识别表中的膨胀。有关gp_bloat_diag的更多信息,请参阅Greenplum数据库参考指南中的"gp_toolkit管理模式"。