MySQL----事务
事务有四个特性:ACID
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。(数据的完整)
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。(并发)
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。(结果)
隔离问题:
- 脏读:一个事务读到了另一个事务没有提交的数据
- 不可重复读:一个事务读到了另一个事务已提交的数据(update)
- 虚读(幻读):一个事务读到了另一个事务已经提交的数据(insert)
事务的四种隔离级别
读事件
- 读未提交:一个事务可以读取到,另外一个事务尚未提交的变更。
- 读已提交:一个事务提交后,其变更才会被另一个事务读取到。
- 可重复读:在一个事务执行的过程中所读取到的数据,和事务启动时所看到的一致。
- 串行化:当操作一行数据时,读写分别都会加锁。当出现读写锁互斥时,会排队串行执行。
参考:https://blog.csdn.net/taylor_tao/article/details/7063639
参考:http://blog.itpub.net/26736162/viewspace-2638951/
- DEFAULT:采用 DB 默认的事务隔离级别。MySql 默认为 REPEATABLE_READ;Oracle 默认为:READ_COMMITTED;
- READ_UNCOMMITTED:一个事务可以读另一个事务未提交数据。未解决任何并发问题。存在3个问题
- READ_COMMITTED:一个事务只能读另一个事务已提交数据(快照更新)。解决脏读,存在不可重复读与幻读。存在2个问题
- REPEATABLE_READ:可重复读,一个事务多次读取一条数据不变(快照不更新)。解决脏读、不可重复读。存在幻读。存在1个问题
- SERIALIZABLE:串行化,一个事务读取数据集合不变。不存在并发问题(效率低)。不存在任何问题
事务原本是数据库中的概念,用于数据访问层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
隔离级别测试:https://blog.csdn.net/qq_26437925/article/details/80270741
写事件
Mysql不管在哪一个隔离级别,如果一个事务对莫一行数据进行了修改,那么其他事务/工具,对这条数据进行修改,就会堵塞。必须等第一个数据提交事务。(Mysql对同一行数据修改是原子事件)
事务开启时间
当执行第一条操作innerDB表的语句的时候,会开启事务,并不是begin/start transaction的时候
Mysql 简单的事务操作
需求:ABCD必须完成
Connection connection = null; try { //1 获得连接connection[可以从连接池中获得] //2 开启事务 connection.setAutoCommit(false); //3 完成ABCD(一系类操作) //A //B //C //D //4 提交事务 connection.commit(); } catch (Exception e) { //5 如果ABCD任何一个操作出现错误,事务回滚 try { connection.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } }finally { //释放资源; try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } }
Mysql 事务操作 Savepoint(保存点)
AB必须完成,CD可以不用完成(例如银行转账之后,给我们手机发送消息)
Connection connection = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置 Savepoint savepoint = null; try { //1 获得连接connection[可以从连接池中获得] //2 开启事务 connection.setAutoCommit(false); //3 完成ABCD(一系类操作) //A //B savepoint = connection.setSavepoint();//添加保存点 //C //D //4 提交事务 connection.commit(); } catch (Exception e) { if (savepoint!=null){ try { //表示CD发生了异常 connection.rollback(savepoint); //将AB提交 connection.commit(); } catch (SQLException ex) { ex.printStackTrace(); } }else{ //AB发送异常 try { connection.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } }finally { //释放资源; try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } }
独占锁、共享锁、更新锁,乐观锁、悲观锁
1、锁的两种分类方式
(1)从数据库系统的角度来看,锁分为以下三种类型:
独占锁(Exclusive Lock)
独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、 UPDATE 或DELETE 命令时,SQL Server 会自动使用独占锁。但当对象上有其它锁存在时,无法对其加独占锁。独占锁一直到事务结束才能被释放。
共享锁(Shared Lock)
共享锁锁定的资源可以被其它用户读取,但其它用户不能修改它。在SELECT 命令执行时,SQL Server 通常会对对象进行共享锁锁定。通常加共享锁的数据页被读取完毕后,共享锁就会立即被释放。
更新锁(Update Lock)
更新锁是为了防止死锁而设立的。当SQL Server 准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。等到SQL Server 确定要进行更新数据操作时,它会自动将更新锁换为独占锁。但当对象上有其它锁存在时,无法对其作更新锁锁定。
(2)从程序员的角度看,锁分为以下两种类型:
悲观锁(Pessimistic Lock)
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
乐观锁(Optimistic Lock)
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
2、数据库中如何使用锁
首先从悲观锁开始说。在SqlServer等其余很多数据库中,数据的锁定通常采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插入机制,在任何时间同一张表只会插1条数据,别的想插入的数据要等到这一条数据插完以后才能依次插入。带来的后果就是性能的降低,在多用户并发访问的时候,当对一张表进行频繁操作时,会发现响应效率很低,数据库经常处于一种假死状态。而Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其余的数据不相干,所以在对Oracle表中并发插数据的时候,基本上不会有任何影响。
Oracle
oracle的悲观锁需要利用一条现有的连接,分成两种方式,从SQL语句的区别来看,就是一种是for update,一种是for update nowait的形式。
(1)for update 形式介绍
然后我们看一下for update锁定方式。我们执行如下的select for update语句:
select * from test where id = 10 for update
通过这条检索语句锁定以后,再开另外一个sql*plus窗口进行操作,再把上面这条sql语句执行一便,你会发现sqlplus好像死在那里了,好像检索不到数据的样子,但是也不返回任何结果,就属于卡在那里的感觉。这个时候是什么原因呢,就是一开始的第一个Session中的select for update语句把数据锁定住了。由于这里锁定的机制是wait的状态(只要不表示nowait那就是wait),所以第二个Session(也就是卡住的那个sql*plus)中当前这个检索就处于等待状态。当第一个session最后commit或者rollback之后,第二个session中的检索结果就是自动跳出来,并且也把数据锁定住。
不过如果你第二个session中你的检索语句如下所示:select * from test where id = 10,也就是没有for update这种锁定数据的语句的话,就不会造成阻塞了。
(2)for update nowait 形式介绍
另外一种情况,就是当数据库数据被锁定的时候,也就是执行刚才for update那条sql以后,我们在另外一个session中执行for update nowait后又是什么样呢。
比如如下的sql语句:
select * from test where id = 10 for update nowait
由于这条语句中是制定采用nowait方式来进行检索,所以当发现数据被别的session锁定中的时候,就会迅速返回ORA-00054错误,内容是资源正忙, 但指定以 NOWAIT 方式获取资源。所以在程序中我们可以采用nowait方式迅速判断当前数据是否被锁定中,如果锁定中的话,就要采取相应的业务措施进行处理。
那这里另外一个问题,就是当我们锁定住数据的时候,我们对数据进行更新和删除的话会是什么样呢。
比如同样,我们让第一个Session锁定住id=10的那条数据,我们在第二个session中执行如下语句:
update test set value=2 where id = 10
这个时候我们发现update语句就好像select for update语句一样也停住卡在这里,当你第一个session放开锁定以后update才能正常运行。当你update运行后,数据又被你update 语句锁定住了,这个时候只要你update后还没有commit,别的session照样不能对数据进行锁定更新等等。
总之,Oracle中的悲观锁就是利用Oracle的Connection对数据进行锁定。在Oracle中,用这种行级锁带来的性能损失是很小的,只是要注意程序逻辑,不要给你一不小心搞成死锁了就好。而且由于数据的及时锁定,在数据提交时候就不呼出现冲突,可以省去很多恼人的数据冲突处理。缺点就是你必须要始终有一条数据库连接,就是说在整个锁定到最后放开锁的过程中,你的数据库联接要始终保持住。
分布式事务
参考博客:https://blog.csdn.net/hh1sdfsf56456/article/details/79449821
数据存储在不同的服务器上,需要多台服务器协同完成任务。所以本质来说,分布式事务就是为了保证不同数据库的数据一致性。
分布式事务系统一致性:https://mp.weixin.qq.com/s?__biz=MzI5ODQ2MzI3NQ==&mid=2247486113&idx=1&sn=bde545f4f5efacb6a68df3ae9a8f1ec1&chksm=eca433e5dbd3baf344af2dd70e46233dd64047a4dd6ac487ba353fb5546da3aa68c65580c3e4&scene=27#wechat_redirect
5种分布式事务解决方案优缺点:https://mp.weixin.qq.com/s?__biz=MzI5ODQ2MzI3NQ==&mid=2247487531&idx=1&sn=b3fbc4dee7cea4a78db062a4a656afdf&chksm=eca4296fdbd3a079a8e328ec7946ced7d1f94c0f105463743a8bee569bae6da00bf2133c3e1a&scene=27#wechat_redirect