mysql中的事务详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nanruitao10/article/details/84071216

事务的四特性

  • 原子性
    原子性是事务不可再分割的单位,事务中的操作要么都执行,要么都不执行。例如:A向B转钱,事务中的A扣款,B加款这两个操作,要么都执行,要么都不执行。
  • 一致性
    一致性是指事务使得系统从一个一致的状态装换到另一个一致状态。例如:A和B的存款总额为10000,A向B转钱,无论成功与否,最终A和B的存款总额依然是10000。
  • 隔离性
    多个事务并发访问时,事务之间是隔离的,一个事务的执行不应该影响其他事务的执行。
  • 持久性
    持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库中,并不会被回滚。

事务的隔离级别

  • 读未提交
    所有的事务都可以读取到其他未提交事务的执行结果,这样会发生脏读。
  • 读已提交
    这是大多数数据库系统默认隔离级别(但不是mysql的)。一个事务之只能读取已经提交事务所做的改变。但是会发生不可重复读,因为一个事务在多次读取数据时,有可能其他事务对这些数据进行修改并提交,导致前一个事务多次读取到的数据不一致。
  • 可重复读
    一个事务的多个实例在并发读取数据时,会看到同样的数据行,这是mysql默认的隔离级别。但是会出现“幻读”,当一个事务读取一定范围内的数据时,其它事务又在该范围内插入或删除了一行数据,当用户再读取该范围的数据行时,会发现新的“幻影”行,这就是幻读。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
  • 可串行化
    这是最高的隔离级别,强制事务排序,使之不可能发生相互冲突。再这个隔离级别下,每个读的数据行加上共享锁,但是可能导致大量的超时现象和锁竞争。

总结:低级别的隔离一般支持更高的并发处理,并拥有更低的系统开销。高级别的隔离可靠性较高,但系统开销较大。

事务并发产生的问题

  1. 赃读(Dirty Read)
    一个事务读取到了另外一个事务没有提交的数据事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
  2. 不可重复读(Nonrepeatable Read)
    在同一事务中,两次读取同一数据,得到内容不同事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
  3. 幻读(Phantom Read)
    同一事务中,用同样的操作读取两次,得到的记录数不相同系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样。

事务中的锁

  1. 悲观锁和乐观锁
    悲观锁:
    悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
    1)使用悲观锁,我们必须关闭mysql数据库的自动提交属性,采用手动提交事务的方式,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。
    2)需要注意的是,在事务中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT … 则不受此影响。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X)。
    3)补充:MySQL select…for update的Row Lock与Table Lock
    使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键(或有索引的地方),MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。

    乐观锁:
    乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做(一般是回滚事务)。那么我们如何实现乐观锁呢,一般来说有以下2种方式:
    1)使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
    2)乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

    总结:两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。另外,高并发情况下个人认为乐观锁要好于悲观锁,因为悲观锁的机制使得各个线程等待时间过长,极其影响效率,乐观锁可以在一定程度上提高并发度。

  2. 共享锁(又称读锁)、排它锁(又称写锁):

    InnoDB引擎的锁机制:InnoDB支持事务,支持行锁和表锁用的比较多,Myisam不支持事务,只支持表锁。

    共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
    排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
    意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
    意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

    说明:

    1)共享锁和排他锁都是行锁,意向锁都是表锁,应用中我们只会使用到共享锁和排他锁,意向锁是mysql内部使用的,不需要用户干预。

    2)对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁,事务可以通过以下语句显示给记录集加共享锁或排他锁。
    共享锁(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。
    排他锁(X):SELECT * FROM table_name WHERE … FOR UPDATE。

    **对于锁定行记录后需要进行更新操作的应用,应该使用Select…For update 方式,获取排它锁。(用共享锁,在读了之后再写会阻塞,会导致死锁)

    这里说说Myisam:MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁。

    3)InnoDB行锁是通过给索引上的索引项加锁来实现的,因此InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

  3. 表锁、行锁
    表级锁(table-level locking):MyISAM和MEMORY存储引擎
    行级锁(row-level locking) :InnoDB存储引擎
    页面锁(page-level-locking):BDB存储引擎

    表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

    行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

    页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

参考:https://segmentfault.com/a/1190000014811125
http://www.cnblogs.com/protected/p/6526857.html

猜你喜欢

转载自blog.csdn.net/nanruitao10/article/details/84071216