演示 无事务引出错误
先说事务,就先创建一个错误,毕竟事务是为了解决这个问题。
#创建一个表
CREATE TABLE t_count(
ucount INT,
uname VARCHAR(10),
umoney DECIMAL(10,2)
);
# 插入数据
INSERT INTO t_count VALUES ('1001','张三',3000);
INSERT INTO t_count VALUES ('1002','李四',2000);
现在看表的数据:
估计创建一个错误。
// 银河 将张三的钱 1000转如李四账户
public static void test_shiwu(){
Connection con=null;
Statement stm=null;
try {
con=getCon();
stm=con.createStatement();
String sql="UPDATE test.t_count SET umoney = umoney-1000 WHERE uname = '张三'";
stm.execute(sql);
//故意创建一个错误
System.out.println(1/0);
sql="UPDATE test.t_count SET umoney = umoney+1000 WHERE uname = '李四'";
stm.execute(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stm.close();
con.close();
}catch (Exception e ){
}
}
}
因为java运行错了,但是不看java的错误,而是看myql表中的数据:
要是银行出现这样事情,还不会被骂死,所以为了解决这样的问题,数据库就引入了事务这个概念。
事务
数据库事务介绍
-
事务:一组逻辑操作单元,使数据从一种状态变换到另一个状态。
-
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使除了故障,都不能改编这种执行方式。当再一个事务中执行多个操作时,要么所有的事务都被提交,那么这些修改就永久低保存下来,要么数据库管理系统将放弃所作的所有修改,整个事务回滚到最初状态。
-
为确保数据库中数据的一致性,数据的操作应当是离散的成组逻辑单元,当天全部完成时,数据的一致性可以保持,而当整个单元中的一部分操作失败,这个事务应全部是为错误,所有的起始点以后的操作应全部回退到开始状态。
其实这个多嘴说一下,操作Mysql的时候数据是自动提交的,而Oralce却是事务需要手动操作的。
而JDBC事务处理会发生什么:
-
数据一旦提交,就不可回滚。
- 当一个链接对象被创建时,默认情况下时自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
- 关闭数据库连接,数据就会自动提交:如果多个操作,每个操作使用的时自己单独的连接,则无法保证事务。即同一个事务的多个操作必须再同一个连接下。
-
JDBC程序中为了让多个SQL语句作为一个事务执行:
-
调用Connection 对象的调用con.setAutoCommit(false);就可以取消自动提交事务。
补充: 如果设置新的Connection 没有被关闭的话,那Connection 还是不手动提交。
-
在所有的SQL语句都成功执行后,需要调用commit();方法调用事务。
-
如果出现异常,就会调用rollback();方法回滚事务。
事务的四特征(ACID)
-
事务由四个特征,下面具体说一下:
- 原子性(Atomicity):原子性是指事务是一个 不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency): 事务必须是数据库从一个一致性状态变换到另外一个一致性状态。
- 隔离性(Isolation):事务的隔离性是指一个事务的执行不能被其它事务干扰,即一个事务内的操作及使用的数据对并发的其它事务时隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改编就是永久性,接下来的其它操作和数据库故障不应对其有任何影响。
因为这四个特征,所以在加入事务的时候解决了上面模拟银行转钱的问题。
添加事务解决演示的错误
public static void test_shiwu(){
Connection con=null;
Statement stm=null;
try {
con=getCon();
con.setAutoCommit(false);
stm=con.createStatement();
String sql="UPDATE test.t_count SET umoney = umoney-1000 WHERE uname = '张三'";
stm.execute(sql);
//故意创建一个错误
System.out.println(1/0);
sql="UPDATE test.t_count SET umoney = umoney+1000 WHERE uname = '李四'";
stm.execute(sql);
con.commit();
} catch (Exception e) {
e.printStackTrace();
try {
// 回滚
con.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
// 修改自动提交
con.setAutoCommit(true);
stm.close();
con.close();
}catch (Exception e ){
}
}
}
还是不管错误,看数据中的表的数据:
数据库并发问题
虽然事务解决了问题,但是又有一个新问题,那就是如果并发同时运行多个事务又会出新问题。
事务的隔离性是指一个事务的执行不能被其它事务干扰,即一个事务内的操作及使用的数据对并发的其它事务时隔离的,并发执行的各个事务之间不能互相干扰。
在前面说四个特征中说到了隔离,但是这个隔离其实又分等级的。如果没有采取必要的隔离机制,就会导致四种并发问题。
- 脏读:对于两个事务A,B。A读取了已经被B更新但还没有被提交的数据。之后,B回滚,那么A读取的内容就是临时且无效的。
- 不可重复读:对于两个事务A,B。A读取了某条数据,这个时候B突然更新了,那么A读取数据的时候指就不同了。
- 幻读:对于两个事务A,B。A读取了数据,然后B这个时候插入了新的数据,之后如果A在读取同一个表,就会多出几行。
一个事务和其它事务隔离的成都称之为隔离级别。数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰成都,**隔离级别越高,数据一致性就越好,但并发性越弱,**所以有的必有失。
数据库中四种隔离级别:
-
READ UNCOMMITTED (读未提交数据)
允许事务读取未被其它事务提交的变更,脏读,不可重复读的问题都会出现
-
READ COMMITTED (读已提交数据)
只允许事务读取已经被其它事务提高的变更,可以避免脏读,但不可重复读取和幻读问题仍然可能出现。
-
REPEATABLE READ(重复读)
确保事务可以多次从一个字段中读取相同的值,这个事务持续期间,禁止其它事务对这个字段进行更新,可以避免脏读和不可重复复读,但幻读的问题仍然存在。
-
SERIALIZABLE (串行)
确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其它事务对该表执行插入,更新和删除操作。所以并发问题都可以避免,但性能十分低下。
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表.
然后整理隔离级别解决三个错误如下:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED (读未提交数据) | 不会解决 | 不会解决 | 不会解决 |
READ COMMITTED (读已提交数据) | 解决 | 不会解决 | 不会解决 |
REPEATABLE READ(重复读) | 解决 | 解决 | 不会解决 |
SERIALIZABLE (串行) | 解决 | 解决 | 解决 |
ORACLE支持2种事务隔离级别:READ COMMITTED ,SERIALIZABLE 而其默认事务隔离为READ COMMITTED 。而Mysql支持4种事务隔离级别,Mysql默认的事务隔离级别为REPEATABLE READ。
mysql 演示
在mysql种查询隔离级别:
SELECT @@tx_isolation;
然后看一下MYSQL是否为自动提交;
SELECT @@autocommit;
这个值为1那么就是自动提交,如果通过set 可以将其设置0就变成不是自动提交。
但是为了方便操作使用
# 开始事务
START TRANSACTION;
更新事务的级别
## 会话级别的
SET TRANSACTION ISOLATION LEVEL 隔离级别 ;
## 全局级别的
SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别 ;
首先需要两个连接,连接某一个数据库,暂时命名A连接和B连接。
只演示一个,不都演示了,不然又要让篇幅很大。具体步骤都写了,如果要全部演示的话按照步骤即可。
READ UNCOMMITTED
A连接
# A开始事务,然后插入数据,但是不提交数据 这两句要一起执行
START TRANSACTION;
INSERT INTO t_count VALUES ('1001','王五',1000);
B连接
#B 查询这个表
SELECT ucount, uname, umoney FROM test.t_count;
然后将A连接回滚:
ROLLBACK;
然后再看B连接的数据:
JDBC演示
public static void test_shiwu(){
Connection con=null;
Statement stm=null;
try {
con=getCon();
con.setAutoCommit(false);
// 得到当前的隔离级别
System.out.println(con.getTransactionIsolation());
// 创建当前的隔离级别
con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
stm=con.createStatement();
String sql="UPDATE test.t_count SET umoney = umoney-1000 WHERE uname = '张三'";
stm.execute(sql);
con.commit();
} catch (Exception e) {
e.printStackTrace();
try {
con.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
// 修改自动提交
con.setAutoCommit(true);
stm.close();
con.close();
}catch (Exception e ){
}
}
}