前述
本篇只打算讲解一些事务和锁的基本概率,关于事务和锁的一些细节和扩展知识,会单独写相应的文章介绍,比如分布式事务,MCVV实现机制。下面是一些链接。
事务特点
Atomicity(原子性):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。
Consistency(一致性):数据库总是从一个一致性状态转换到另一个一致状态。
Isolation(隔离性):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。注意这里的“通常来说”。
Durability(持久性):一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。
事务隔离级别
未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。
提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。
可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读。
串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
锁的类型
共享锁,两个事务可以同时读。
排他锁,只要一个事务获取到锁,另一个事务读和写都需等待锁释放。
锁的粒度
行锁,InnoDB默认粒度。
页锁,行锁可以通过锁升级升级为行锁。
表锁,MyISAM默认的粒度。InnoDB的意向锁(意向共享锁和意向排它锁)也是表锁。
锁的一些问题
丢失更新
定义:事务T1读取了数据,并执行了一些操作,然后更新数据。事务T2也做相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引起错误。
解决方法:将事务串行化,给事务加排它锁,等事务T1读取并更新数据库完成,事务T2在读取更新数据库。
脏读
定义:所谓脏读,就是一个事务可以读到另一个事务未提交的数据。当锁级别是READ UNCOMMIT(未提交读)时,就会产生脏读。
解决方法:将锁的隔离级别改成READ COMMIT即可防止脏读。
不可重复读
定义:在第一个事务两次读取同一数据之间,第二个事务对数据做了更新,使得第一个事务两次读到的数据不一致。不可重复读强调更新,幻读强调插入和删除。
解决方法:将第一个事务的两次读捆绑成一个整体的事务。将锁的隔离级别改成REPEATED READ。
幻读
所谓幻读,就是同一个事务,用同样的范围查询读取两次,得到的记录数不相同。
解决方法:利用范围锁RangeS RangeS_S模式,锁定检索范围为只读,这样就避免了幻读问题。或者直接将事务隔离级别改成Serializable。InnoDB通过多版本并发控制(MVCC)解决幻读。
死锁
定义
死锁发生在并发的情况。一个事务申请资源时,需要获得锁,如果锁已经被其他事务获取(排它锁),这时这个事务就将阻塞等待锁的释放。如果这两个事务互相等待,就造成死锁。
解决方法
锁检测和锁超时。如果获取锁超时,自动放弃获取锁,并将事务回滚,将已经获得的锁释放。
死锁产生的四个必要条件
1)互斥条件:对所需要获取的资源进行排他性控制;
2)不可剥夺条件:所需要的资源在锁未释放前,不可被其他事务夺走;
3)请求和保持条件:事务已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求事务被阻塞,但对自己已获得的资源保持不放。
4)循环等待链:存在一种事务资源的循环等待链,形成一个等待环。
多版本并发控制(MVCC)
MVCC读不加锁,读写不冲突,提高系统并发性能。不同的数据库和存储引擎对MVCC实现都不同,下面以InnoDB的实现为例。
MySQL会给每一行增加两个隐藏列:DATA_TRX_ID
和DATA_ROLL_PTR
,分别代表操作该行的最新事务id和undo指针。事务id在每次执行都会自动加1。
MVCC有下面几个特点(看起来有点乐观锁的味道):
1)每行数据都存在一个版本。
2)每次数据更新时都更新该版本 修改时Copy出当前版本随意修改,个事务之间无干扰。
3)保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)。
对于SELECT操作,InnoDB只查找版本号必须小于等于事务版本的数据行。这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行。
对于UPDATE操作,InnoDB会先加排它锁,把行原本记录拷贝到undo log。产生一个新的版本号(事务id加1),DATA_TRX_ID
存储新的事务id,DATA_ROLL_PTR
指向原记录undo log,然后更新值。
对于INSERT操作,插入新记录,DATA_TRX_ID
存储最新的事务id。
对于DELETE操作,DATA_TRX_ID
记录删除该事务id。当然这里是软删除,真正删除在事务commit时。