2013年7月30日星期二

Mysql并发控制

最近在看Mysql的并发控制,事务处理等知识,做些整理。
并发控制目的是当多个连接对数据库进行修改时保证数据的一致性。现在mysql的InnoDB在update,delete时使用行级锁,对于select会结合MVCC保证一致性。
1、 并发控制
MySQL提供两个级别的并发控制:服务器级(the server level)和存储引擎级(the storage engine level)。加锁是实现并发控制的基本方法,MySQL中锁的粒度:
(1)    表级锁:MySQL独立于存储引擎提供表锁,例如,对于ALTER TABLE语句,服务器提供表锁(table-level lock)。
(2)    行级锁:InnoDB和Falcon存储引擎提供行级锁,此外,BDB支持页级锁。InnoDB的并发控制机制,下节详细讨论。
另外,值得一提的是,MySQL的一些存储引擎(如InnoDB、BDB)除了使用封锁机制外,还同时结合MVCC机制,即多版本两阶段封锁协议MVCC(Multiversion two-phrase locking protocal),来实现事务的并发控制,从而使得只读事务不用等待锁,提高了事务的并发性。

数据库的事务处理的原则是保证ACID的正确性。
2、事务处理
2.1 事务的ACID特性
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性:
(1)原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
(2)一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
(3)隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
(4)持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
2.2、事务处理带来的相关问题
由于事务的并发执行,带来以下一些著名的问题:
(1)更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新。
(2)脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"。
(3)不可重复读(Non-Repeatable Reads):一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
(4)幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

3. Mysql隔离级别:
READ UNCOMMITTED :事务可以看到其他事务没有被提交的数据(脏数据)
READ COMMITTED :事务可以看到其他事务已经提交的数据
REPEATABLE READ :事务中两次查询的结果相同。(Innodb默认级别)
SERIALIZABLE :所有事务顺序执行,对所有read操作加锁。保证一致性。

4. 注意点:
(1)InnoDb通过MVCC防止了幻读(Phantom Read),即两次select之间有另一个事务插入了数据并且commit,两次select也是保证一致性的。但是如果事务B的insert/update/delete语句执行时间和commit时间在本事务A的第一个select之前,则事务A的第一个select是可以读到事务B的更新数据的。

(2)如果一个事务A,先进行了update/insert/delete操作,在进行第一次select操作,那么select是可以读到更新的时间,这样造成的结果是select可能读到根本不存在的数据(http://dev.mysql.com/doc/refman/5.1/en/innodb-consistent-read.html 

(3)如果一个事务A进行了select操作,那么在这个select开始后的其他事务B进行了对数据的更新操作(update/insert/delete),事务A的select将不会看到事务B操作的结果。事务Aselect在遍历过程中会遇到两种情况,一种是当前遍历的行时正在被更新(update/insert/delete),那么这一行会被加上write lock锁。另一种是,当前遍历的行没有被加锁。

对于第一种请,Innodb处理方式是select过程中出逐行遍历数据,如果某一行被write lock了,那么会去undo段里面找这一行的前一个snapshot数据,这个snapshot数据是由write操作引起的,每次write操作都将保存当前事务时刻的原始数据,用于write失败进行回滚,所以是放在undo数据段里面。

对于第二种情况:可以通过事务的编号进行过滤,innodb的每个事务都有个事务编号保存在每一行数据的隐藏字段里,如果当前事务编号比数据记录里面的隐藏字段的事务编号小,那么表示当前数据是被当前事务后面的事务更新的,那么不取当前数据,而是去undo段里取snapshot数据。
(Mysql文档的原文是:Suppose that you are running in the default REPEATABLE READ  isolation level. When you issue a consistent read (that is, an ordinary SELECT  statement), InnoDB gives your transaction a timepoint according to which your query sees the database. If another transaction deletes a row and commits after your timepoint was assigned, you do not see the row as having been deleted. Inserts and updates are treated similarly.)

没有评论:

发表评论