事务和锁
一、什么是事务
事务是逻辑上的一组数据库操作,要么都执行,要么都不执行。
例子:假如张三要给李四转账200元,这个转账会涉及到两个关键操作就是:将张三的余额减少200元,将李四的余额增加200元。如果两个操作之间突然出现错误,例如银行系统崩溃导致张三余额减少,而李四的余额没有增加,这样的系统是有问题的。事务就是保证这两个关键操作要么都成功,要么都要失败。
事务的特性
①原子性:事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;例如转账的这两个关键操作(将张三的余额减少200元,将李四的余额增加200元)要么全部完成,要么全部失败。
②一致性: 确保从一个正确的状态转换到另外一个正确的状态,这就是一致性。例如转账业务中,将张三的余额减少200元,中间发生断电情况,李四的余额没有增加200元,这个就是不正确的状态,违反一致性。又比如表更新事务,一部分数据更新了,但一部分数据没有更新,这也是违反一致性的;
③隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
④持久性:一个事务被提交之后,对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
二、事务之间的相互影响
事务之间的相互影响分为几种,分别为:脏读,不可重复读,幻读,丢失更新
脏读
事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的账户,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。
当事务 T1 正在访问字段 A 并且对进行了修改,而这种修改还没有提交到数据库中。这时另外一个事务 T2 也访问和使用字段 A,但由于事务 T1 修改字段 A 后还没有提交COMMIT
,而那么事务 T2 读到的字段 A 是**“脏数据”**。
丢失更新
第一类丢失更新: 撤销一个事务的时候,把其它事务已提交的更新数据覆盖了。这是完全没有事务隔离级别造成的。如果事务1被提交,另一个事务被撤销,那么会连同事务1所做的更新也被撤销。
第二类丢失更新:当两个或多个事务查询相同的记录,然后各自基于查询的结果更新记录时会造成第二类丢失更新问题。每个事务不知道其它事务的存在,最后一个事务对记录所做的更改将覆盖其它事务之前对该记录所做的更改。
例如:事务 T1 读取 A=20,事务 T2 也读取 A=20,事务 T1 修改 A=A-1,事务 T2 也修改 A=A+1,最终结果 A=21,事务 T1 的修改被丢失。
不可重复读
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…
不可重复读取是指同一个事务在整个事务过程中对同一笔数据进行读取,每次读取结果都不同。如果事务1在事务2的更新操作之前读取一次数据,在事务2的更新操作之后再读取同一笔数据一次,两次结果是不同的。
不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这回导致锁竞争加剧,影响性能。
幻读
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉。
在同一个事务中,同一个查询多次返回的结果不一致。事务A新增了一条记录,事务B在事务A提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录, 就好像发生了幻觉一样。
幻读是由于并发事务增加记录导致的,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。
三、数据库隔离级别及原理
锁类型简述
共享锁(S锁):假设事务T1对数据A加上共享锁,那么事务T2可以读数据A,不能修改数据A。
排他锁(X锁):假设事务T1对数据A加上共享锁,那么事务T2不能读数据A,不能修改数据A。
读未提交
Read Uncommitted:最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
原理:
事务对当前读取的数据不加锁;事务对数据更新前添加行级共享锁,直到事务结束才释放。
可能发生的情况:
事务1读取某些数据记录时,事务2也能对这些记录进行读取、更新;当事务2对这些记录进行更新时,事务1再次读取记录,能读到事务2对该记录的修改版本,即使更新尚未提交。事务1更新某些数据记录时,事务2不能对这行记录做更新,直到事务1结束。
简单地理解就是:
允许事务同时读数据允许一个事务读取数据同时另外一个事务修改数据必须等更新数据的事务执行完成后,才能对执行其他的读取或者修改该数据的事务
读已提交
Read Committed:只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
原理:
事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放。
可能发生的情况:
事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,读到的只能是事务2对其更新前的版本,要不就是事务2提交后的版本。事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。
简单地理解就是:
允许事务同时读数据必须一个事务读取完数据后,另外一个事务才能修改该数据必须等更新数据的事务执行完成后,才能对执行其他的读取或者修改该数据的事务
可重复读
Repeated Read:在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
原理:
事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加行级共享锁,直到事务结束才释放;事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放。
可能发生的情况:
事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,读到的仍然是第一次读取的那个版本。事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。
简单地理解就是:
允许事务同时读数据必须等读取数据的事务执行完成后,才能对执行其他的修改该数据的事务必须等更新数据的事务执行完成后,才能对执行其他的读取或者修改该数据的事务
可序列化
事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加行级共享锁,直到事务结束才释放;事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放。Serialization:事务串行化执行,隔离级别最高,牺牲了系统的并发性。可以解决并发事务的所有问题。
可能发生的情况:
事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,读到的仍然是第一次读取的那个版本。事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。
简单地理解就是:
所有的事务必须等上一个事务执行完成后才开始执行
参考:
数据库的4种隔离级别
数据库隔离级别 及 其实现原理
【原创】惊!史上最全的select加锁分析(Mysql)
数据库隔离级别及原理解决方案
【原创】惊!史上最全的select加锁分析(Mysql)
数据库隔离级别及原理解决方案