mysql数据一致性和副本复制-part1

    以mysql(InnoDB)作为实例,讨论一下mysql(InnoDB)在数据存储一致性和副本复制方面的一些实现机制。由于不是专门从事mysql相关的研发和运维工作,本文主要是依据公开的资料并结合一些实际使用经验探讨InnoDB存储引擎,如有谬误,欢迎指出。
    mysql作为数据存储软件的一种实现,必然要满足规定的数据存储一致性要求,同时作为一个广泛使用的数据存储软件,在可靠性、可用性以及性能方面也必须表现良好,这离不开数据复制。
    注意:本文所讨论的“一致性”其含义不同于ACID模型中的Consistency特性,默认情况下本文中的“一致性”特指数据存储一致性,当提及的是ACID模型中的Consistency(一致性)时用黑体加粗标识。
1、数据存储一致性
    mysql(InnoDB)和众多数据库有一个共同特性即均支持ACID事务模型,ACID用于保证数据库事务被可靠的处理。在数据库上下文环境中,事务是指对数据所做的一个单一的逻辑操作,它强调的是操作在逻辑概念上的单一性,而在实际实现时可能会被分解成多个具体的操作步骤。比如从一个银行账户转账到另一个账户,这是一个单一的事务,但涉及到记入和记出等多个变更操作。
1.1 ACID模型
(1)Atomicity(原子性):一个事务中所包含的多个操作步骤是一个不可分割的执行单元,要么全部正常执行,要么一个也没执行,不存在部分操作成功或部分操作失败的状态。
(2)Consistency(一致性):一个事务应确保数据库从一种有效状态转变到另一种有效的状态,有效状态是指写入到数据库中的任何数据必须符合所有定义的规则,包括:约束条件(constraints)、级联规则(cascades)、触发器(triggers)以及其它组合规则。
(3)Isolation(隔离性):决定一个事务的完整性是如何被其它事务或其它操作所感知,是数据库并发控制要达到的主要目标(数据一致性要求)。
(4)Durability(持久性):一旦一个事务完成,其执行结果应被持久化存储,即便随后发生断电、崩溃等突发性故障。
1.2 隔离级别
(1)Read Uncommitted(读未提交):允许脏读,事务可以看到其它事务尚未提交的变更,该隔离级别实际中无意义,只存在理论中。
(2)Read Committed(读提交):仅保证事务读到的任何数据均是已经被其它事务提交过的,存在不可重复读的现象(只是现象,不是问题)。
(3)Repeatable Read(可重复读):仅保证事务可以做到重复读,存在幻读现象(因为没有强制要求对读操作增加range locks)。
(4)Serializable(串行化):串行化执行事务,该隔离级别实际中无意义,只存在理论中。
    在ACID模型中和数据存储一致性直接相关的就是隔离性,它决定了并发控制的目标,而并发控制的目标实质上就是实现某种数据存储一致性。隔离性描述数据库是如何解决并发进程在访问数据库时出现的竞争问题(race conditions)。在传统的教科书中通常会把隔离性的含义描述成类似serializability,这样事务就好像是一个接一个的执行,并发问题就不会出现。但是在隔离性的实际实现中,串行化很少被使用,这是出于可用性和性能的的考虑。ACID模型和性能(高并发)之间是相互冲突的,数据库通过提供可选的隔离性在性能和安全性之间进行折中,较弱的隔离性响应速度更快但是也会带来更多的潜在的并发竞争冲突。在InnoDB中,缺省的隔离级别是Repeatable Read,InnoDB使用NextKeyLock来解决幻读问题,使得数据一致性效果上等同于串行化而又不失并发的粒度和性能。
1.3 MVCC (Multi-Version Concurrency Control)
    InnoDB在实现ACID事务模型时使用了多版本并发控制(MVCC)技术来减少锁的使用,以提高并发量、吞吐量和性能,显然这是以降低数据一致性的强度作为代价的。InnoDB通过把MVCC和锁机制等结合起来共同实现ACID模型中的隔离性要求。MVCC使得用户进程能够看到数据库在某个时间点的一致性状态(这里指满足数据库规则的快照数据),即使其它并发进程已经修改了数据库,这是通过保留数据行的旧版本来实现的。当更新一行时,如果仍有事务需要读取该行,则旧版本的行将被保留。MVCC只工作在Read Committed和Repeatable Read级别,在不同的隔离级别下MVCC工作模式也不相同。
    如上所述,InnoDB采用MVCC机制来实现事务的隔离性,MVCC的基本思想是为不同的事务保存其当时数据的快照(称为“版本”),它既能保证事务内数据的一致性,又避免了锁的大量使用,从而提升了性能和并发度。InnoDB中的MVCC机制在逻辑上实际为每行数据建立了两个版本:创建版本(INSERT)和过期版本(DELETE);但在实现时并不是如此的直观,而是从UNDO 日志中找到一行数据的创建版本和多个历史过期版本。InnoDB对每一数据行(聚簇索引叶子节点)都会增加三个额外字段和一个删除标记位:DATA_TRX_ID、DATA_ROLL_PTR、DB_ROW_ID、DELETE BIT。其中,6字节的DATA_TRX_ID标记了最新更新行记录的transaction id;7字节的DATA_ROLL_PTR是回滚指针,指向当前行记录的UNDO信息,找之前版本的数据就是通过这个指针,UNDO日志中的记录(行)保存了旧的DB_TRX_ID 和DB_ROLL_PTR,可能指向更久之前的事务和UNDO日志记录。DELETE BIT位用于标识该记录是否被删除;6字节的DB_ROW_ID单调非递减,当表中没有可用的聚簇索引时,会将其作为聚簇索引。
    在InnoDB默认的“Repeatable Read”隔离级别下,当执行各类SQL命令时MVCC机制的工作方式如下(如未特别指明,以下所说的行均指聚簇索引中的行记录,在这里不讨论执行SQL命令时二级索引的变化情况,以免过于庞杂):
(1)INSERT:InnoDB使用DB当前版本号作为新增行的版本号(创建版本)。
(2)DELETE:InnoDB使用DB 当前版本号作为行的删除号(过期版本),该行并未被立即物理删除。具体做法是:设置行的删除控制位标记行已被删除,将被删除行的原始数据写入UNDO日志(不改变原来行的DB_TRX_ID),更新聚簇索引中行的版本号(DB_TRX_ID )和回滚指针(DB_ROLL_PTR) 。
(3)UPDATE
    a. 更新主键,聚簇索引/二级索引均无法进行in place update,均会产生两个版本;
    b. 更新非主键索引,聚簇索引可以in place update;二级索引产生两个版本;
    c. 聚簇索引记录undo,二级索引不记录undo;
    d. 更新聚簇索引,记录旧版本会进入rollback segment undo page;
    e. 更新二级索引,同时需要判断是否修改索引页面的MAX_TRX_ID;
    f. 属于同一事务的undo记录,在undo page中保存逆向指针。
    更新主键时聚簇索引中行的变化情况是:此时UPDATE相当于先INSERT新行再DELETE旧行,InnoDB先新增一行,并把DB当前版本号作为新增行的版本号;接着“删除”原来行,即把删除控制位置位,但该行并未被立即物理删除,并将被删除行的原始数据(不改变原来行的DB_TRX_ID)写入UNDO日志,对应于新增行的回滚指针。更新非主键时聚簇索引中行的变化情况是:将原来行的记录写入UNDO日志(不改变原来行的DB_TRX_ID),然后直接在本行记录上做更新。
(4)SELECT
    a. 事务中第一次simple select时建立快照;如果开启事务后立即执行了select for update或者update等,则不会建立快照。
    b. 快照涵盖整个数据库中的数据,不仅仅是select时查询到的数据。
    c. 除了simple select是快照读之外,其它都是当前读。在执行select操作时,InnoDB对比当前事务的版本号和找到的行的创建版本号/删除版本号,但仅选择同时满足以下两个条件的行:
    c.1 行的创建版本号不大于当前事务的版本号。这确保了该行在事务开始时已存在,或者由当前事务创建、更新。
    c.2 行的删除版本不存在或者删除版本大于当前事务的版本号。这确保事务开始前行未被删除。

你可能感兴趣的:(分布式理论与实践)