一、事务 mysql
1.一、事务的基本概念和使用示例sql
数据库事务,是指做为单个逻辑工做单元执行的一系列操做,要么完整地执行,要么彻底地不执行。 事务处理能够确保除非事务性单元内的全部操做都成功完成,不然不会永久更新面向数据的资源。经过将一组相关操做组合为一个要么所有成功要么所有失败的单元,能够简化错误恢复并使应用程序更加可靠。一个逻辑工做单元要成为事务,必须知足所谓的ACID(原子性、一致性、隔离性和持久性)属性。数据库
JDBC能够操做Connection的setAutoCommit()方法,给它false参数,提示数据库启动事务,在下达一连串的SQL命令后,自行调用Connection的commit()方法,提示数据库确认(Commit)操做。若是中间发生错误,则调用rollback(),提示数据库撤销(ROLLBACK)全部执行。同时,若是仅想要撤回某个SQL执行点,则能够设置存储点(SAVEPOINT)。一个示范的事务流程以下:安全
Connection conn = ...; Savepoint point = null; try { conn.setAutoCommit(false); Statement stmt = conn.createStatement(); stmt.executeUpdate("INSERT INTO ..."); ... point = conn.setSavepoint(); stmt.executeUpdate("INSERT INTO ..."); ... conn.commit(); } catch (SQLException e) { e.printStackTrace(); if (conn != null) { try { if (point == null) { conn.rollback(); } else { conn.rollback(point); conn.releaseSavepoint(point); } } catch (SQLException ex) { ex.printStackTrace(); } } } finally { ... if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ex) { ex.printStackTrace(); } } }
须要说明的是,JDBC操做事务的前提条件是数据库支持事务,若是数据库自己不支持事务,那么咱们即便调用setAutoCommit(false)也没法启动事务。对于MYSQL来讲,MyIsam数据库引擎不支持事务操做,InnoDB数据库引擎支持事务操做。 网络
1.二、隔离级别并发
要理解隔离级别,首先要了解多个事务并行时,可能引起的数据不一致问题有哪些,常见的事务并行引起问题有如下几类:分布式
更新遗失函数
更新遗失是指某个事务对字段进行更新的信息,因另外一个事务的介入而遗失更新的效力,一个简单的示例以下:性能
这个序列就是典型的更新丢失,由于第三步所作的全部修改所有会丢失。若是要避免更新遗失问题,能够设置隔离级别为"read uncommitted",这样A事务已更新但未确认的数据,B事务仅可作读取操做,但不可作更新操做。这样上面的四个步骤就是不合法的,必须A事务彻底提交,B事务才能作更新操做。spa
脏读
"read uncommitted"隔离级别保证了在A事务提交以前,B事务不能作更改操做,可是没有阻止B事务作读取操做,可是这个实际上是有问题的:若是A事务更新字段为"AAA",B事务读取值为"AAA"并使用,而后A事务回滚事务,那么B事务读取的"AAA"就属于脏数据。若是要避免脏读问题,能够设置隔离级别为"Read Commited",也就是事务读取的数据必须是其余事务已经确认的数据。
不可重复读
不可重复度是指两次读取同一字段的数据不一致,例如:事务A读取字段为"AAA",事务B更新数据为"BBB",事务B提交,事务A读取字段为"BBB",事务A连续的两次读取,字段值不同。要避免这种问题,能够设置数据库隔离级别为"Repeatable Read",对于这种隔离界别,事务A读取字段为"AAA"后,其余事务在事务A提交前只可读取该字段,不可更新该字段。
幻读
幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,好比这种修改涉及到表中的"所有数据行"。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入"一行新数据"。那么,之后就会发生操做第一个事务的用户发现表中仍是有没有修改的数据行,就好象发生了幻觉同样。要解决幻读问题,必须设置隔离级别为Serializable,Serializable是数据库隔离级别的最高级别,串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读,可是执行效率慢,须要谨慎使用。
可使用如下命令查询mysql的隔离级别:
select @@global.tx_isolation,@@tx_isolation;
1.三、悲观锁、乐观锁
悲观锁是采用一种悲观的态度来对待事务并发问题,认为系统中的并发更新会很是频繁,而且事务失败了之后重来的开销很大,这样一来,咱们就须要采用真正意义上的锁来进行实现。悲观锁的实现,每每依靠数据库提供的锁机制。悲观锁的基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等之前的事务提交或者回滚解除锁。
乐观锁,顾名思义就是保持一种乐观的态度,咱们认为系统中的事务并发更新不会很频繁,即便冲突了也没事,大不了从新再来一次。它的基本思想就是每次提交一个事务更新时,咱们先看看要修改的东西从上次读取之后有没有被其它事务修改过,若是修改过,那么更新就会失败。乐观锁的实现方式大可能是基于数据版本 ( Version )记录机制实现。何谓数据版本?即为数据增长一个版本标识,在基于数据库表的版本解决方案中,通常是经过为数据库表增长一个 “version” 字段来实现。 读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。
总的来讲,悲观锁的机制依赖数据库的锁机制,较安全,而乐观锁机制经过应用程序控制,性能较好。
1.四、分布式事务
分布式事务处理涉及多个分布在不一样地方的数据库,但对数据库的操做必须所有被提交或者回滚。只要任一数据库操做时失败,全部参与事务的数据库都须要回滚。
Open 组织定义的分布式事务处理模型X/Open DTP 模型包括应用程序( AP )、事务管理器( TM )、资源管理器( RM ,即数据库 )、通讯资源管理器( CRM )四部分。而 XA 是 X/Open DTP 定义的事务管理器与数据库之间的接口规范(即接口函数),事务管理器用它来通知数据库事务的开始、结束以及提交、回滚等。
XA 接口规范使用两阶段提交协议来完成一个全局事务,保证同一事务中全部数据库同时成功或者回滚。
两阶段提交协议假设每一个数据库点都存在一个write-ahead log,每一次的write请求都是先记log后才真正执行写入。
第一阶段为提交请求阶段(Commit-request phase):
1. 事务管理器给全部数据库发query to commit消息请求,而后开始等待回应;
2. 数据库若是能够提交属于本身的事务分支,则将本身在该事务分支中所作的操做固定记录下来(在undo log和redo log中各记一项);
3. 数据库都回应是否赞成提交的应答。
第二阶段为提交阶段(Commit phase):
若是事务管理器收到的全部回应都是agreement,
1. 事务管理器记日志并给全部数据库发commit消息请求;
2. 各个数据库执行操做,释放全部该事务相关的锁和资源;
3. 各个数据库给事务管理器回复;
4.当收到全部回复,事务管理器结束当前事务
若是事务管理器收到的任一回应是abort,
1. 事务管理器记日志并给全部数据库发rollback消息请求;
2. 各个数据库执行undo操做,释放全部该事务相关的锁和资源;
3. 各个数据库给事务管理器回复;
4.当收到全部回复,事务管理器结束当前事务
两阶段提交协议的问题在于数据库在提交请求阶段应答后对不少资源处于锁定状态,要等到事务管理器收集齐全部数据库的应答后,才能发commit或者rollback消息结束这种锁定。锁定时间的长度是由最慢的一个数据库制约,若是数据库一直没有应答,全部其余库也须要无休止的锁并等待。而且,若是事务管理器出现故障,被锁定的资源将长时间处于锁定状态。不管是任一数据库或者事务管理器故障,其余数据库都须要永久锁定或者至少长时间锁定。而且,分布式系统中节点越多,存在缓慢网络或者故障节点的几率也就越大,资源被长时间锁定的几率指数上升。
两阶段提交协议的另外一个问题是只要有任意一个数据库不可用都会致使事务失败,这致使事务更倾向于失败。对于多个副本的备份系统,不少时候咱们但愿部分副本点失效时系统仍然可用,使用该协议则不能实现。而且,分布式系统中节点越多,存在故障节点的几率也就越大,系统的可用性指数降低。
另外,若是数据库在第一阶段应答后到第二阶段正式提交前的某个阶段网络故障或者节点故障,该协议没法提交或回滚,数据不一致不能绝对避免。
二、存储过程
JDBC能够调用存储过程,要调用存储过程,首先咱们应该建立存储过程:
//建立表,并插入数据 create table g(num int,value varchar(10)); insert into g values(1, '1'),(10, '10'),(60, '60'),(100, '100'); //建立存储过程 DELIMITER $ CREATE PROCEDURE p1(IN n int, OUT avg double, OUT min int) BEGIN select avg(num) from g where num > n INTO avg; select min(num) from g where num > n INTO min; END$ DELIMITER ;
JDBC调用存储过程的接口与增删改查的不一样,JDBC调用存储过程应该使用CallableStatement,简单示例以下:
private static void ps() throws SQLException{ Connection conn = null; CallableStatement cs = null; try{ conn = JdbcUtils.getConnection(); cs = conn.prepareCall("call p1(?,?,?)"); cs.registerOutParameter(2, Types.DOUBLE);//设置out参数 cs.registerOutParameter(3, Types.INTEGER);//设置out参数 cs.setInt(1, 18);//设置in参数 cs.executeUpdate(); System.out.println(cs.getInt(2) + " " + cs.getInt(3)); } finally{ JdbcUtils.free(null, cs, conn); } }
三、批次更新
若是须要对对数据库进行大量数据更新,使用循环屡次操做更新是比较浪费性能的,对于这种场景,咱们可使用addBatch()方法来收集SQL,并使用executeBatch()方法将收集的SQL批次更新,例如:
Statement stmt = conn.createStatement(); while(someCondition) { stmt.addBatch("INSERT INTO ..."); } stmt.executeBatch();