mysql的事务和锁

来源:互联网 发布:淘宝助理违规验证通过 编辑:程序博客网 时间:2024/06/05 18:14

1 事务的ACID特性

mysql引入事务的目的是为了保证数据操作的一致性,有了事务之后,用户可以把一组操作定义为原子性的操作。mysql的事务具有ACID的特性。

  • 原子性(atomicity):一个事务定义了一组操作,其实mysql执行的不可分割的最小工作单位。事务中的操作要么全部成功,要么全部失败,不会出现只有部分成功的情况。
  • 一致性(consistency):一致性定义了mysql数据状态只能从一个一致性状态到另外一个一致性状态,其保证用户不会读取到原子操作中的部分中间结果。
  • 持久性(durability):持久性是指事务提交后,其修改不会因为系统的稳定性而丢失,如mysql系统崩溃、服务器断电等。
  • 隔离性(isolation):其表明的是事务与事务之间的可见性,其中事务被定义了四种隔离级别,未提交读、提交读、可重复读、串行化。其中mysql默认隔离级别可重复读,其保证事务中的读取操作在任何时间段读取的结果都是一样的,并且不会受其他事务的影响(提交或者未提交)。

    在我们具体介绍事务的这四个特性之前还需要介绍下mysql中锁的概念。其中锁在是否加锁的上分为乐观锁和悲观锁,其中乐观锁是一种通过多版本控制的方式来实现类似加锁的操作,其本质上是每次对数据的更新都会改变数据的版本,并且在回写的时候回检查当前版本是不是修改之前的版本,如果不是证明已经有其他程序更改过该数据,这样的回写操作会失败。如此来保证了数据的一致性。其中mvcc就是mysql的乐观锁实现。悲观锁则是需要在数据上进行加锁从而来保证数据的一致性,其中有根据锁的并发粒度分为共享锁(读锁)和互斥锁(写锁)。共享锁是指所有的读操作都能共享的使用同一锁从而提高读锁的并发度。互斥锁是指所有的所有加了互斥锁的数据,不能能够再加读锁和写锁,使得需要加锁的读和写操作都要等待该互斥锁的释放。在mysql中是通过悲观锁来解决幻读的问题。

1.1 事务的原子性实现(undo log)

mysql实现原子性操作是同对每个一个事务的操作都记录一个undo log,当事务执行失败时,通过undo log来回滚到事务的初始状态。同时由于事务的隔离性保证了这样的回滚操作不会影响其他的事务,因为其他事务还是读取该事务开始时的数据。对于写操作会加互斥锁,因此通过undo log回滚到事务的开始状态也不会出现数据覆盖导致变更丢失的危险。

Undo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。

用Undo Log实现原子性和持久化的事务的简化过程:假设有A、B两个数据,值分别为1,2。

  1. 事务开始,记录A=1到undo log.
  2. 修改A=3.
  3. 记录B=2到undo log.
  4. 修改B=4.
  5. 将undo log写到磁盘。
  6. 将数据写到磁盘。
  7. 事务提交

这里有一个隐含的前提条件:‘数据都是先读到内存中,然后修改内存中的数据,最后将数据写回磁盘’。之所以能同时保证原子性和持久化,是因为以下特点:

  • 更新数据前记录Undo log。
  • 为了保证持久性,必须将数据在事务提交前写到磁盘。只要事务成功提交,数据必然已经持久化。
  • Undo log必须先于数据持久化到磁盘。如果在6,7之间系统崩溃,undo log是完整的,可以用来回滚事务。
  • 如果在1-6之间系统崩溃,因为数据没有持久化到磁盘。所以磁盘上的数据还是保持在事务开始前的状态。

1.2 事务的持久性实现(redo log)

其实如果按照上述undo的方式进行实现那么数据的持久性已经得到保证了,但是其有一个很大的缺点:每个事务提交前将数据和Undo Log写入磁盘,这样会导致大量的磁盘IO,因此性能很低。并且根据2-8原理该写入的数据很快的会重新被读取出来,如果需要每次把变更的数据写入硬盘之后,再进行读取,不仅吞吐量低,并且磁盘IO会成倍数增加。因此如果能够将数据缓存一段时间,就能减少IO提高性能。但是这样就会丧失事务的持久性,事务提交了但是数据并没有写入磁盘,如果系统崩溃就会操作数据的丢失。因此引入了另外一种机制来实现持久化,即Redo Log.
和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态。
Undo + Redo事务的简化过程:假设有A、B两个数据,值分别为1,2.

  1. 事务开始.记录A=1到undo log.
  2. 修改A=3.
  3. 记录A=3到redo log.
  4. 记录B=2到undo log.
  5. 修改B=4.
  6. 记录B=4到redo log.
  7. 将undo log 和 redo log写入磁盘。
  8. 事务提交

Undo + Redo事务的特点

  • 为了保证持久性,必须在事务提交前将Redo Log持久化。
  • 数据不需要在事务提交前写入磁盘,而是缓存在内存中。
  • Redo Log 保证事务的持久性。
  • Undo Log 保证事务的原子性。
  • 有一个隐含的特点,数据必须要晚于redo log写入持久存储。

数据恢复策略:

  • 已经提交的事务通过redo log进行恢复。
  • 未提交的事务通过undo log 进行回滚。

1.3 事务的隔离性

mysql的默认隔离级别为可重复读,其实现方式是MVCC(多版本控制)。mvcc的主要作用是保证同一个事务的多次的同一个读操作前后两次读的结果是一致的,其原理是在事务中第一次读取时会记录数据库中记录的版本,即使另外一个事务对数据进行更改并已提交,在后续读取时依然是以前版本的数据,不是最新版本,其类似于快照数据,因此对于事务中的读取操作都是快照读,并且事务之间的中间结果是互相不可见的。注意:没有事务的读取操作是读取的最新版本的数据,没有隔离性。对于在事务中(select * from xxx lock in share mode 或者 select * from xxx for update)会申请加锁,因此对于后续需要修改的操作都会被阻塞。因此对于需要加锁的操作读取的都是最新的数据,又叫当前读(当前版本对应于快照读)。多版本控制就是指在数据库中同时存在多个版本的数据,正式由于多版本控制保证了mysql的事务隔离级别为可重复读。

1.4 事务的一致性

由于事务的原子性和隔离性保证了数据只会从一个一致性状态变到另外一个一致性。因此原子性保证一个事务的一组操作只会同时成功或者同时失败,再加上数据的隔离性保证了事务间的一组操作的中间结果是不可见的。从而最终保证了数据的一致性。

2 加锁解决幻读

什么是幻读:http://blog.csdn.net/cweeyii/article/details/70991230
何登成mysql博客:http://hedengcheng.com/?p=771
在mysql事务中根据是否加锁可以分为两类操作:

  1. 快照读,简单的select操作,属于快照读,不加锁。 select * from table where ?;
  2. 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。select * from table where ? lock in share mode;select * from table where ? for update; insert into table values (…); update table set ? where ?; delete from table where ?;
    所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。
    对于快照读和当前读都是在第一次读取时进行版本控制或者加锁,和事务的开始时间没有关系。
0 0
原创粉丝点击