白话事务
一、概述
事务是一系列的动作(狭义上的事务特指数据库读写事务),它们综合在一起才是一个完整的工作单元,这些动作要么全部完成,要么全部失败。(如果有一个失败的话,那么事务就会回滚到最开始的状态)
事务概述二、特性(ACID)
原子性(Atomicity),事务必须是一个原子的操作序列单元,一次事务只允许存在两种状态,全部成功或全部失败,任何一个操作失败都将导致整个事务失败
一致性(Consistency),事务的执行不能破坏系统数据的完整性和一致性,如果未完成的事务对系统数据的修改有一部分已经写入物理数据库,这时系统数据就处于不一致状态
隔离性(Isolation),在并发环境中,不同的事务操作相同的数据时,相互隔离不能相互干扰(由锁机制来实现)
持久性(Durability),事务一旦提交,对系统数据的变更就应该是永久的,必须被永久保存下来,即使服务器宕机了,只要数据库能够重新启动,就一定能够恢复到事务成功结束时的状态
三、事务并发处理问题
如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题。由于并发操作带来的数据不一致性包括:丢失数据修改、读”脏”数据(脏读)、不可重复读、产生幽灵数据。
举个栗子:
数据库记录3.1 丢失数据
a:第一类丢失更新(lost update)
在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。
回滚丢失b:第二类丢失更新(second lost updates)
不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。
覆盖丢失3.2 数据错误
a:脏读(dirty read)
如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读
事务没提交,提前读取b:虚读(phantom read)
一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的
虚读c:不可重复读(unrepeated read)
一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任
不可重复读四、事务并发处理方案
解决并发问题的途径是什么?
答案是:采取有效的隔离机制。
怎样实现事务的隔离呢?
答案是:隔离机制的实现必须使用锁
数据库系统提供四种事务隔离级别:
未提交读(READ UNCOMMITTED )
最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;
提交读(READ COMMITTED)
一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不会出现丢失更新、脏读,但可能出现不可重复读、幻读;
可重复读(REPEATABLE READ)
保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,不可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
序列化(SERIALIZABLE)
最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。
四种事务隔离级别一般推荐使用REPEATABLE READ级别保证数据的读一致性。对于幻读的问题,可以通过加锁来防止
隔离与并发关系图MySQL支持这四种事务等级,默认事务隔离级别是REPEATABLE READ。
五、锁
5.1 乐观锁(Optimistic Lock)和悲观锁(Pessimistic Lock)
最重要的分类就是乐观锁(Optimistic Lock)和悲观锁(Pessimistic Lock)
乐观锁:顾名思义就是非常乐观,非常相信真善美,每次去读数据都认为其它事务没有在写数据,所以就不上锁,快乐的读取数据,而只在提交数据的时候判断其它事务是否搞过这个数据了,如果搞过就rollback。乐观锁相当于一种检测冲突的手段,可通过为记录添加版本或添加时间戳来实现。
悲观锁:对其它事务抱有保守的态度,每次去读数据都认为其它事务想要作祟,所以每次读数据的时候都会上锁,直到取出数据。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性,但随之而来的是各种开销。悲观锁相当于一种避免冲突的手段。
备注:如果并发量不大,或数据冲突的后果不严重,则可以使用乐观锁;而如果并发量大或数据冲突后果比较严重(对用户不友好),那么就使用悲观锁。
5.2 分共享锁(S锁,Shared Lock)和排他锁(X锁,Exclusive Lock)
从读写角度,分共享锁(S锁,Shared Lock)和排他锁(X锁,Exclusive Lock),也叫读锁(Read Lock)和写锁(Write Lock)。
备注:持有S锁的事务只读不可写。如果事务A对数据D加上S锁后,其它事务只能对D加上S锁而不能加X锁。持有X锁的事务可读可写。如果事务A对数据D加上X锁后,其它事务不能再对D加锁,直到A对D的锁解除。
5.3 表级锁(Table Lock)和行级锁(Row Lock)
从锁的粒度角度,主要分为表级锁(Table Lock)和行级锁(Row Lock)。
表级锁将整个表加锁,性能开销最小
用户可以同时进行读操作。当一个用户对表进行写操作时,用户可以获得一个写锁,写锁禁止其他的用户读写操作。写锁比读锁的优先级更高,即使有读操作已排在队列中,一个被申请的写锁仍可以排在所队列的前列。
行级锁仅对指定的记录进行加锁
这样其它进程可以对同一个表中的其它记录进行读写操作。行级锁粒度最小,开销大,能够支持高并发,可能会出现死锁。
MySQL的MyISAM引擎使用表级锁,而InnoDB支持表级锁和行级锁,默认是行级锁。
5.4 三级锁协议
三级加锁协议是为了保证正确的事务并发操作,事务在读、写数据库对象是需要遵循的加锁规则。
一级封锁协议:事务T在修改数据R之前必须对它加X锁,直到事务结束方可释放。而若事务T只是读数据,不进行修改,则不需加锁,因此一级加锁协议下可能会出现脏读和不可重复读。
二级加锁协议:在一级加锁协议的基础上,加上这样一条规则——事务T在读取数据R之前必须对它加S锁,直到读取完毕以后释放。二级加锁协议下可能会出现不可重复读。
三级加锁协议:在一级加锁协议的基础上,加上这样一条规则——事务T在读取数据R之前必须对它加S锁,直到事务结束方可释放。三级加锁协议避免了脏读和不可重复读的问题
六、参考资料
1、https://www.jianshu.com/p/05b70834dafe