数据库事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。 通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。
一个数据库事务通常包含对数据库进行读或写的一个操作序列。它的存在包含有以下两个目的:
1、为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
2、当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
commit即提交,表示这个事务的所有操作都执行成功,commit告诉系统,数据库要进入一个新的正确状态,该事务对数据库的所有更新都要确保不因数据库的宕机而丢失。 rollback即回退或回滚,表示事务中有执行失败的操作,这些操作必须被撤销,rollback告诉系统,已发生错误,数据库可能处在不正确的状态,该事务对数据库的部分或所有更新必须被撤销。
commit的作用不是把缓存/内存中的数据更新/同步到数据库。
commit 之前,数据已经提交到服务器了,只是写在日志里,没有正式提交。所以可以被另外的客户端读取。
1.读事务
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性; 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
2.写事务
很好理解,如银行转账A用户需要扣钱B用户需要加钱。
首先程序是可以并发执行的,同样,在MySQL中,一个表可以由两个或多个进程同时来读写数据。 比如,此时有两个进程来读数据,这也没什么问题,允许。但是如果一个进程在读某一行的数据的过程中,另一个在进程又往这一行里面写数据(改、删),那结果会是如何?同样,如果两个进程都同时对某一行数据进行更改,以谁的更改为准?那结果又会怎样,不敢想象,是不是数据就被破坏掉了。所以此时是冲突的。
某一时刻同时发来3000个请求指定进程,进程中单线程执行的话那相当与3000个读写操作同时发生。
一个进程中开3000个线程同时对数据库进行读写操作。
并发事务所作的修改必须与任何其它并发事务所作的修改隔离。
事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据,这称为隔离性。
上面四中都是事务并发时可能会产生的影响,按照影响性大小的不同又被称为事务的隔离级别。
既然会冲突就要想办法解决,靠谁来解决,这时候就是靠锁机制来维护了。怎么使用锁来使他们不冲突? 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
🔐排他锁:被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象。 🔐共享锁:被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加共享锁。 特别的,对共享锁:如果两个事务对同一个资源上了共享锁,事务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)。对封锁方式规定不同的规则,就形成了各种不同的封锁协议。
何时用锁,用什么锁,什么时候释放锁,都是数据库层面的事情,不需要我们去关心。以及下面要讲到的封锁协议,都是数据库已经拟定好的了,什么时候对行加锁,对表加锁在协议中指定好了,我们到时候只需选择事务的隔离级别就好了。
一级封锁协议是:事务在对需要修改的数据上面(就是在发生修改的瞬间) 对其加共享锁(其他事务不能更改,但是可以读取-导致“脏读”),直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。 一级封锁协议不能避免 丢失更新,脏读,不可重复读,幻读!
二级封锁协议是: 1)事务在对需要更新的数据上(就是发生更新的瞬间)加排他锁(直到事务结束),防止其他事务读取未提交的数据,这样,也就避免了 “脏读” 的情况。 2)事务对当前被读取的数据上面加共享锁(当读到时加上共享锁),一旦读完该行,立即释放该该行的共享锁。 二级封锁协议除防止了“脏读”数据,但是不能避免 丢失更新,不可重复读,幻读 。 二级封锁协议中,由于读完数据后立即释放共享锁,所以它不能避免可重复读,同时它也不能避免丢失更新,如果事务A、B同时获取资源X,然后事务A先发起更新记录X,那么事务B 将等待事务A执行完成,然后获得记录X 的排他锁,进行更改。这样事务A的更新将会被丢失。
三级封锁协议是:二级封锁协议加上事务在读取数据的瞬间必须先对其加共享锁 ,但是直到事务结束才释放,这样保证了可重复读(既是其他的事务职能读取该数据,但是不能更新该数据)。 三级封锁协议除防止了“脏”数据 和不可重复读 。但是这种情况不能避免 幻读和丢失更新的情况。 在事务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所覆盖!
四级封锁协议是对三级封锁协议的增强,其实现机制也最为简单,直接对 事务中 所 读取 或者 更改的数据所在的表加表锁,也就是说,其他事务不能 读写 该表中的任何数据。这样所有的 脏读,不可重复读,幻读 ,都得以避免!
附:Mysql中不同级别锁的介绍 页级:引擎 BDB。 表级:引擎 MyISAM , 理解为锁住整个表,可以同时读,写不行 行级:引擎 INNODB , 单独的一行记录加锁 表级,直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作。如果你是写锁,则其它进程则读也不允许 行级,,仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。 页级,表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。 备注:Mysql的默认隔离级别是 可重复读(Repeatable-Read) 当设置事务隔离级别为Read Ucommitted的时候,一个事务(A)能读取到另一个事务(B)修改后【未提交】的数据。