数据库事务探究
数据库事务(Database Transaction)
1. 事务是个啥玩意?
数据库事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。
通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。
2. 事务都有啥属性特征呢?
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。
3. 事务这玩意能干啥?
一个数据库事务通常包含对数据库进行读或写的一个操作序列。它的存在包含有以下两个目的:
1、为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
2、当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
4. 追根究底的将事务之前,先问问自己commit和rollback的还知道是干啥的不?
commit即提交,表示这个事务的所有操作都执行成功,commit告诉系统,数据库要进入一个新的正确状态,该事务对数据库的所有更新都要确保不因数据库的宕机而丢失。
rollback即回退或回滚,表示事务中有执行失败的操作,这些操作必须被撤销,rollback告诉系统,已发生错误,数据库可能处在不正确的状态,该事务对数据库的部分或所有更新必须被撤销。
commit的作用不是把缓存/内存中的数据更新/同步到数据库。
commit 之前,数据已经提交到服务器了,只是写在日志里,没有正式提交。所以可以被另外的客户端读取。
5. 都有哪些事务?
1.读事务
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
2.写事务
很好理解,如银行转账A用户需要扣钱B用户需要加钱。
6. 事务并发执行时会产生哪些影响?
首先程序是可以并发执行的,同样,在MySQL中,一个表可以由两个或多个进程同时来读写数据。
比如,此时有两个进程来读数据,这也没什么问题,允许。但是如果一个进程在读某一行的数据的过程中,另一个在进程又往这一行里面写数据(改、删),那结果会是如何?同样,如果两个进程都同时对某一行数据进行更改,以谁的更改为准?那结果又会怎样,不敢想象,是不是数据就被破坏掉了。所以此时是冲突的。
某一时刻同时发来3000个请求指定进程,进程中单线程执行的话那相当与3000个读写操作同时发生。
一个进程中开3000个线程同时对数据库进行读写操作。
7. 隔离级别是个啥?
并发事务所作的修改必须与任何其它并发事务所作的修改隔离。
事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据,这称为隔离性。
- 读未提交:(Read Uncommitted)
- 读已提交(Read Committed) 大多数数据库默认的隔离级别
- 可重复读(Repeatable-Read) mysql数据库所默认的级别
- 序列化(serializable)
上面四中都是事务并发时可能会产生的影响,按照影响性大小的不同又被称为事务的隔离级别。
8. 啥情况需要隔离,那又咋隔离呢?
既然会冲突就要想办法解决,靠谁来解决,这时候就是靠锁机制来维护了。怎么使用锁来使他们不冲突?
1.丢失更新
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。
如:两个事务都需要更新同一条记录中的Name字段
时间点 | 事务A | 事务B |
---|---|---|
1 | 事务开始 | |
2 | 读取Name=‘张三’ | 事务开始 |
3 | 读取Name=‘张三’ | |
4 | 更新Name=‘毛蛋’ | |
5 | 事务结束 | 更新Name=‘狗子’ |
6 | 事务结束 |
事务A:‘老子的毛蛋怎么没了!’
事实上Mysql数据库会在事务里面默认添加写锁,上面的现象是没法重现的。
2.读未提交(脏读)
脏读又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。
事务T1修改了一行数据,但是还没有提交,这时候事务T2读取了被事务T1修改后的数据,之后事务T1因为某种原因Rollback了,那么事务T2读取的数据就是脏的。
时间点 | 事务A | 事务B |
---|---|---|
1 | 事务开始 | |
2 | 读取Name=‘张三’ | 事务开始 |
3 | 更新Name=‘狗子’ | |
4 | 读取Name=‘狗子’ | |
5 | 事务回滚Name=‘张三’ | |
6 | 读取Name=‘张三’ | |
7 | 事务结束 | 事务结束 |
事务A:‘中间咋突然冒出个狗子,真纳闷!’
解决办法:把数据库的事务隔离级别调整到READ_COMMITTED
3.读已提交(不可重复读)
不可重复读是指在同一个事务内,两个相同的查询返回了不同的结果。
事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。
时间点 | 事务A | 事务B |
---|---|---|
1 | 事务开始 | |
2 | 读取Name=‘张三’ | 事务开始 |
3 | 更新Name=‘狗子’ | |
4 | 读取Name=‘张三’ | |
5 | 事务结束 | |
6 | 读取Name=‘狗子’ | |
7 | 事务结束 |
事务A:‘在我的一个事务里先后读取同样的记录结果却不一样,真奇怪。’
解决办法:把数据库的事务隔离级别调整到REPEATABLE_READ
4.幻读
和可重复读类似,但是事务二的数据操作仅仅是插入和删除,不是修改数据,读取的记录数量前后不一致。
按一定条件从数据库中读取了某些记录后,T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录消失。
T1按一定条件从数据库中删除某些数据记录后,T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。
不可重复读侧重表达 读-读,幻读则是说 读-写,用写来证实读的是鬼影。
时间点 | 事务A | 事务B |
---|---|---|
1 | 事务开始 | |
2 | 查询到有两个人的Name=‘张三’ | 事务开始 |
3 | 将所有Name=‘张三’的Score变成100分 | |
4 | 插入Name=‘张三’且Score=60 | |
5 | 读取到3条Name=‘张三’且有一条得分为60 | 事务结束 |
6 | 事务结束 |
事务A:‘怎么多出来一条数据?还是60分,难不成我刚才眼花了,还是产生了幻觉?’
解决办法:把数据库的事务隔离级别调整到SERIALIZABLE_READ
9. 锁机制实现事务隔离
两把锁
🔐排他锁:被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象。
🔐共享锁:被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加共享锁。
特别的,对共享锁:如果两个事务对同一个资源上了共享锁,事务A想更新该数据,那么它必须等待事务B释放其共享锁。【共享锁=S锁】又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
【排他锁=X锁】又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
两把锁的使用规则
在运用🔐排他锁和🔐共享锁对数据对象加锁时,还需要约定一些规则,例如何时申请 排他锁 或 共享锁、持锁时间、何时释放等。称这些规则为封锁协议(Locking Protocol)。对封锁方式规定不同的规则,就形成了各种不同的封锁协议。
何时用锁,用什么锁,什么时候释放锁,都是数据库层面的事情,不需要我们去关心。以及下面要讲到的封锁协议,都是数据库已经拟定好的了,什么时候对行加锁,对表加锁在协议中指定好了,我们到时候只需选择事务的隔离级别就好了。
一级封锁协议(对应read uncommitd)
一级封锁协议是:事务在对需要修改的数据上面(就是在发生修改的瞬间) 对其加共享锁(其他事务不能更改,但是可以读取-导致“脏读”),直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。
一级封锁协议不能避免 丢失更新,脏读,不可重复读,幻读!
二级封锁协议(对应read commit)
二级封锁协议是:
1)事务在对需要更新的数据上(就是发生更新的瞬间)加排他锁(直到事务结束),防止其他事务读取未提交的数据,这样,也就避免了 “脏读” 的情况。
2)事务对当前被读取的数据上面加共享锁(当读到时加上共享锁),一旦读完该行,立即释放该该行的共享锁。
二级封锁协议除防止了“脏读”数据,但是不能避免 丢失更新,不可重复读,幻读 。
二级封锁协议中,由于读完数据后立即释放共享锁,所以它不能避免可重复读,同时它也不能避免丢失更新,如果事务A、B同时获取资源X,然后事务A先发起更新记录X,那么事务B 将等待事务A执行完成,然后获得记录X 的排他锁,进行更改。这样事务A的更新将会被丢失。
三级封锁协议(对应reapetable read)
三级封锁协议是:二级封锁协议加上事务在读取数据的瞬间必须先对其加共享锁 ,但是直到事务结束才释放,这样保证了可重复读(既是其他的事务职能读取该数据,但是不能更新该数据)。
三级封锁协议除防止了“脏”数据 和不可重复读 。但是这种情况不能避免 幻读和丢失更新的情况。
在事务A没有完成之前,事务B可以新增数据,那么当事务A再次读取的时候,事务B新增的数据会被读取到,这样,在该封锁协议下,幻读就产生了。
如果事务A和事务B同时读取了资源X=100,同样,如果事务A先对X进行更新X=X+100,等待事务A执行完成X=200,那么事务B 获得X的排他锁,进行更新X=X+200,然后提交 X=300,同样A的更新被B所覆盖!
最强封锁协议(对应Serialization)
四级封锁协议是对三级封锁协议的增强,其实现机制也最为简单,直接对 事务中 所 读取 或者 更改的数据所在的表加表锁,也就是说,其他事务不能 读写 该表中的任何数据。这样所有的 脏读,不可重复读,幻读 ,都得以避免!
附:Mysql中不同级别锁的介绍
页级:引擎 BDB。
表级:引擎 MyISAM , 理解为锁住整个表,可以同时读,写不行
行级:引擎 INNODB , 单独的一行记录加锁表级,直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作。如果你是写锁,则其它进程则读也不允许
行级,,仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
页级,表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。备注:Mysql的默认隔离级别是 可重复读(Repeatable-Read)
当设置事务隔离级别为Read Ucommitted的时候,一个事务(A)能读取到另一个事务(B)修改后【未提交】的数据。
出处:http://www.cnblogs.com/lingyejun/
若本文如对您有帮助,不妨点击一下右下角的【推荐】。
如果您喜欢或希望看到更多我的文章,可扫描二维码关注我的微信公众号《翎野君》。
转载文章请务必保留出处和署名,否则保留追究法律责任的权利。