数据库事务管理小结

  • 格式:pdf
  • 大小:768.62 KB
  • 文档页数:11

数据库事务管理小结--by 小马哥前面一篇小结<数据库学习小结(JDBC-DAO)>是本文小结的基础,我觉得需要对数据库的事物管理单独作为一个章节来总结一下,否则有可能说不清楚.先来个引子,其实就是把<数据库学习小结(JDBC-DAO)>文中第八章节的内容拷过来了.一.jdbc 对事物的控制事物本身,也不想多讲了.事物本身的定义可以参照书本<数据库系统概论>里面的讲解.这里只大致介绍一下jdbc 对事物的支持,jdbc 是通过commit()和rollback()两个函数来实现的.书本里面,对事物的举例,用的是一个转账的例子,张三给李四转账1000元,不能扣了张三的钱之后李四的钱却没加上去,这就是一个事物.我这里由于不想改代码,所以我就不写转账的示例代码了,下面的例子,我们假定添加"李莉"和"李刚"两个操作,是一个原子性的操作,来理解下面代码(还是仔细看注释):看代码中标黄的位置,我们最初把其注释掉(假定数据库之前没有"李莉"和"李刚"这两个人的数据).OK,执行完了之后查看数据库:发现"李莉"被添加了进来,但是"李刚"并没有被添加进去.而且控制台的输出打印了"haha2",说明后面添加李刚的代码并没有被执行到,而是走到异常处理中去了.OK,删掉数据库中"李莉"的数据,然后把注释的两行加上,再执行代码,发现控制台的打印依旧存在,但是"李莉"的数据如我们所愿,也没有被写入到数据库,这就和我们假定的是一样了.二.数据库隔离级别第一章节中,我们通过例子可以看到,jdbc 是通过commit()和rollback()两个函数来实现事物的提交和回滚(rollback 本文中没有给出范例代码).我们先来看看下面的这个表格table1,它是个抽象的概念,表示数据库的几种隔离级别,有的数据库(如mysql)支持很多种隔离级别,可以自己配置.有的就只支持很简单的一两种.表格中的V 表示,有可能会出现这种情况,x 表示,这种情况不会发生.隔离级别的作用是能够保证多个线程同时操作一条数据时的正确性,它是一个非常重要的概念,也是一个不容易理解透的概念.希望我能讲清楚:读未提交(Read 数据,还没有进行提交,别人就可以看到,这个时候就会产生脏读,可以重复读,幻读了.读已提交(Read committed):他的隔离级别稍微高一些,意思就是说当一个线程的操作事务没有提交,别人是无法读取的得到的,但是他还是不能解决不可重复读和幻读.可重复读(Repeatable read ):隔离级别更高了,它是mysql 的默认隔离级别,它能保证不会读到其他线程未提交的数据,也能保证每次读到的数据是一样的,但是不能避免幻读的问题.可串行化(Serializable ):这是级别最高的隔离级别,它可以避免脏读,不可重复读,幻读的问题.隔离级别越高,对数据正确性的保证会越好,但同时牺牲很多的数据库性能,并发性会越差,具体的调整需要根据自己的需要进行,建议不要设置为可串行化与读未提交这样的级别,当然各个数据库提供对隔离级别的支持是不一样的,有些数据库压根就没有实现和支持相应的隔离级别。

ok ,我们还是通过在mysql 数据库中来操作一遍,根据例子来理解上面的那个表格table1,在做实验之前,先必须了解一个mysql 的知识.要记住mysql 有一个autocommit 参数,默认是on,它的作用是每一条单独的查询都是一个事务,并且自动开始,自动提交.这种模式会在每条语句执行完毕后把它作出的修改立刻提交给数据库并使之永久化.事实上,这相当于把每一条语句都隐含地当做一个事务来执行.如果你想明确地执行事务,需要禁用自动提交模式并告诉MySQL 你想让它在何时提交或回滚有关的修改.我们来看看默认的mysql 中,这个autocommit 模式是什么:可以看到,autocommit 默认是打开的.执行事务的常用办法是发出一条START TRANSACTION (或BEGIN )语句挂起(暂停)自动提交模式,然后执行构成本次事务的各条语句(即从start transaction 语句开始,到commit 语句结束这中间的语句),最后用一条COMMIT 语句结束事务并把它们作出的修改永久性地记入数据库.万一在事务过程中发生错误,用一条ROLLBACK 语句撤销事务并把数据库恢复到事务开始之前的状态.START TRANSACTION 语句"挂起"自动提交模式的含义是:在事务被提交或回滚之后,该模式将恢复到开始本次事务的START TRANSACTION 语句被执行之前的状态.(如果自动提交模式原来是激活的,结束事务将让你回到自动提交模式;如果它原来是禁用的,结束当前事务将开始下一个事务)比如说这个就表示,一个线程有可能会读取到另外一个线程的未提交的某个数据OK,下面开始实验:1.同时打开两个mysql的cmd窗口,模拟两个线程来操作数据库.2.查询mysql当前隔离级别的方法为:select@@tx_isolation;我们假设数据库原始状态如下:实验1:在第一个窗口里面做如下操作,注意执行完insert into student values('200200001','李莉','女',19,'CS');语句之后,并没有执行commit;命令提交事物.这时候在本窗口查询student表可以看到已经添加进去了:在第二个窗口里面做查询操作,如下,说明第一个窗口里面的修改并没有对第二个窗口的读数据产生影响.:然后回到第一个窗口,执行commit语句:再回到第二个窗口,执行查询语句,这里可以看到,当第一个窗口执行完commit之后,第二个窗口再查询,就能够查询到第一个窗口中的事物添加进去的数据了:实验2:实验一中,我们两个cmd窗口用的都是mysql默认的隔离模式,现在,我们来尝试一下mysql的Read uncommitted模式.请先把数据库恢复到原始状态.在窗口2中设置@@tx_isolation这个数据库全局的参数,如下:至于命令"SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;"本文后续会讲到,这里先不要关注,只需要知道它可以设置我们窗口2的隔离模式为"读未提交"就行了(注意窗口1的隔离模式仍然为"可重复读").这里,我们在窗口1中执行如下命令,但是不做commit操作:然后在窗口2中执行查询语句,可以看到,窗口1的事物还没有commit,窗口2就已经可以读到窗口1中所做的更改了:再在窗口1执行commit操作:然后继续在窗口2中查询,结果不变:实验3:用命令SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;设置窗口2,然后将数据库恢复到原始状态,如下图窗口2.在窗口1中执行如下命令:然后在窗口2中查询,发现窗口1的添加操作没有对窗口2的查询操作有影响:然后再窗口1中执行commit:窗口2中再查询:实验4:将窗口2的隔离模式恢复到默认的隔离模式SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;,同时恢复数据库到原始状态:在窗口1中执行如下语句:然后在窗口2中执行如下语句,发现窗口查询语句,李勇的性别仍然为男,并没有改变.当执行update操作时,没有返回值,一直挂死在那里了,其实这里说明了一个问题,mysql肯定是在执行事物时,对"李勇"这一行加了共享锁的,就是只可以读,不可以写.所以当窗口2要去执行写操作的时候,线程会一直等待,直到窗口1的线程把共享锁释放掉:我们紧接在在窗口1中执行commit命令(窗口1的图略),观察下图中窗口2的变化,窗口2马上就返回执行成功了(当然如果你时间太久了也可能会报超时),而且发现,"李勇"的性别和专业都已经发生了改变:后面的实验不想做了,有这4个实验基本已经能够了解到数据库隔离模式的意义了,对于可串行化这个我就不想再做实验了.下面回到实验2中,命令"SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;"的使用方法,其实说白了就是设置某一个窗口(线程),或者全局的隔离模式的命令:它的语法如下:SET [SESSION |GLOBAL]TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED |READ COMMITTED |REPEATABLE READ |SERIALIZABLE}注意:默认的行为(不带session 和global )是为下一个(未开始)事务设置隔离级别。

如果你使用GLOBAL 关键字,语句在全局对从那点开始创建的所有新连接(除了不存在的连接)设置默认事务级别。

你需要SUPER 权限来做这个。

使用SESSION 关键字为将来在当前连接上执行的事务设置默认事务级别。

任何客户端都能自由改变会话隔离级别(甚至在事务的中间),或者为下一个事务设置隔离级别。

你可以用下列语句查询全局和会话事务隔离级别:SELECT @@global.tx_isolation;SELECT @@session.tx_isolation;SELECT @@tx_isolation;网上在设置隔离级别的时候也有一些错误的用法:如:mysql>set @@tx_isolation='read-committed';Query OK,0rows affected (0.00sec)但事实上,session 事物的隔离级别并没有改变三.Spring 的编程式事务管理先说下什么是spring 的编程式事务管理,实际上,spring 提供了两种对事物进行管理的方式:一种就是本章节要讲的编程式事务管理,另外一种就是声明式事务管理.编程式事务管理:就是在程序代码中直接对事物进行控制.声明式事务管理:就是利用spring 的AOP 编程技术(需要先学习文档《Spring AOP 学习小结》知道了AOP 的概念才可以理解)将事务从业务代码中分离出来,让你可以仅仅通过xml 文件就可以来管理事物.本章节要讲的就是编程式的事物管理.OK,先必须要介绍的是,spring 通过下面几个类来管理"事物的属性","事物的操作","事物的当前状态",如下:事实上,在后面的例子中,我们能看到spring 主要也就是通过在xml 里面配置这几个类的参数,从而实现对数据库事物的控制.OK,废话不多说,来看我们的例子!public interface PlatformTransactionManager {//本类专注用来执行事物的操作动作,比如说提交,回滚操作,获取事物当前状态的操作TransactionStatus getTransaction(TransactionDefinition definition);//所以说它里面定义的全是一些动作,操作throws TransactionException;void commit(TransactionStatus status)throws TransactionException;void rollback(TransactionStatus status)throws TransactionException;}public interface TransactionDefinition {//这个类主要就是定义的事物的一些固有属性,比如说属性名,隔离级别,事物超时时间等int getPropagationBehavior();int getIsolationLevel();String getName();int getTimeout();boolean isReadOnly();}public interface TransactionStatus extends SavepointManager {//这个类就是定义的事物的状态,比如说是否完成,有没有保存点等boolean isNewTransaction();boolean hasSavepoint();void setRollbackOnly();boolean isRollbackOnly();boolean isCompleted();}例子之前我们得描述一下当前数据库的状态,student 表和sc 表如下(这俩表都是在<数据库系统概论>课本里面建起来的表),我们假设现在想要添加一个学生"200200001李莉女19CS",并且同时在sc 表中添加李莉的课程号为2的成绩(课程号为另外一个表course 中的,详见课本).好,我们认为添加学生同时添加成绩这两个操作为一个原子性的事物,要么都添加,要么都不添加.数据库原始状态如下(两个表,student 和sc):看代码(详细看注释,下面代码中,大部分代码与文档<spring 框架下配置JDBC 访问数据库小结>第一章节中的代码一样,其中不一样的地方我会用黄色打底标记):其中,新增的sc.java 文件,相当于是对数据库中另外一个表建立的一个文件.另外,studentDAO.java 文件中,新增了一个对本例中所定义事物的一个处理函数//student.java 文件,这个文件完全没有变化package springProgTransaction;class student {private String sno ;private String sname ;private String ssex ;private int sage ;private String sdept ;public String getSno(){return sno ;}public void setSno(String sno ){this .sno =sno ;}public String getSname(){return sname ;}public void setSname(String sname ){this .sname =sname ;}public String getSsex(){return ssex ;}public void setSsex(String ssex ){this .ssex =ssex ;}public int getSage(){return sage ;}public void setSage(int sage ){this .sage =sage ;}public String getSdept(){return sdept ;}public void setSdept(String sdept ){this .sdept =sdept ;}}//sc.java 这个文件是完全新加的package springProgTransaction;public class sc {private String sno ;private String cno ;private int grade ;public String getSno(){return sno ;}public void setSno(String sno ){this .sno =sno ;}public String getCno(){return cno ;}public void setCno(String cno ){this .cno =cno ;}public int getGrade(){return grade ;}public void setGrade(int grade ){this .grade =grade ;}}/*studentDAO.java 这个文件增加了一个函数,用于本例中对"添加学生,同时添加成绩"这一事物的操作*/package springProgTransaction;interface studentDAO {public int addStudent(student s );public int delStudent(student s );public int updateStudent(student s );public int queryStudent();public int addStudentAndsc(student s ,String cno ,int grade );}本文件中标黄的部分就是实现事物的部分.实验结果:1.可以看到,当代码执行完毕之后,数据库student和sc表中并没有加入任何项,也就是说,事物没有执行成功. "添加学生"和"添加成绩"这两件事是作为一个原子性的事物被提交的,由于if后面的语句执行不了,所以"添加学生"的操作也无法完成.2.注释掉addStudentAndsc()函数里面的if(1==1)那两行语句,再执行代码,发现student和sc表中,添加成功了我们预期的元组项.3.把数据库恢复到原始状态,同时把if(1==1)语句还原.注释掉下面几行语句:TransactionDefinition def=new DefaultTransactionDefinition();TransactionStatus status=transactionManager.getTransaction(def);mit(status);我们会发现,"添加学生"这件事完成了,而"添加成绩"没有完成.这说明这两件事已经不能构成一个事物了.总结,所以事物的本身,是通过transactionManager先是获取到一个事物的当前状态,然后执行commit来实现了,再通俗点来说,就是通过被这三行语句所包围的代码构成一个事物,否则就不认为是一个事物.spring就是通过这种方式来管理编程式的事物.四.Spring的声明式事务管理spring的声明式事物管理我在前面第三章节已经讲过其定义了.下面还是通过例子来讲解:其中,数据库的原始状态和第三章节中的例子保持一致.源码本章中也不完全贴出,跟第三章基本一样,本章只对修改的部分做改动,其实改动很少的.实验结果:1.执行一下代码,发现student表和sc表中都没有被添加进去,跟预期符合.说明已经执行了回滚操作.2.注释掉if语句中的throw new RuntimeException("simulate Error condition");语句,发现表中的student表和sc表中都可以正常加入数据.3.还原数据库到原始状态,将return-1;语句打开,执行之后,发现表中student添加了数据,sc中无数据.总结,所以说,spring的声明式事物管理,实际上是以一个一个的方法为单位的,就是说你在实现一个事物的时候,必须要把事物中所有要干的活写在同一个方法里面,然后spring就可以通过这个函数是否抛出异常来判断该事物是否已经执行完毕,从而来执行commit或者回滚操作.所以一个事物其实现的单位就是一个方法!它不同于编程式的事物管理,通过三行语句包起来一段代码块来确定这段代码块就是一个事物.。