事务
事务概述
在一个事件中,有许多单元组成,这些单元要么全部成功,或者全部失败,这些单元全部组成起来称之为一个事务。
- 案例
比如简单的转账业务,sql实现
开启事务
start transaction;
update account set money = money-100 where name ='小明';
update account set money = money+100 where name ='小红';
commit; 或者 rollback;
由于这个事件需要同时发生或者同时不发生,所以需要把这两句sql写在一个事务中。不开启事务,mysql默认一条语句为一个事务。
-
JDBC事务案例
在JDBC中也是一条语句为一个事务,如果需要多条sql语句在同一个事务中,就需要开启事务。
conn.setAutoCommit(boolean) | 开启事务。默认值位true。代表开启自动提交,一句sql就是一个事务。修改为false,代表关闭自动提交,意味着开启使用,多个sql可以在同一个事务中执行。 |
conn.commit() | 提交事务 |
conn.rollback() | 回滚事务 |
conn.rollback(Savepoint sp) | 回滚到记录点 |
- JDBC代码实现
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//事务测试
public class TransDemo1 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
SavePoint sp = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
//开启事务
conn.setAutoCommit(false);//默认值为true,代表会自动提交。
//自动提交意味着一句sql就是一个事务,执行sql就会立刻提交这个事务,修改数据库中的数据。
//设置为false,代表不会自动提交,表示开启事务,需要手动提交或回滚。
ps = conn.prepareStatement("update user set money = money -100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
//设置事务回滚点
//sp = conn.setSavePoint();
int i =1/0;
ps = conn.prepareStatement("update user set money = money +100 where name = ?");
ps.setString(1, "b");
ps.executeUpdate();
conn.commit();//在sql书写完成之后,选择使用commit方法提交事务。
} catch (Exception e) {
if(conn !=null){
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
} }
e.printStackTrace();
}finally{
if(conn !=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}finally{
conn = null;
} }
if(rs !=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally{
rs = null;
} }
if(ps !=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}finally{
ps =null;
} } } } }
事务特性
- 事务四大特性(ACID)
原子性(Atomicity):一个事务中多个单元,是不能分割的,要么同时成功,要么同时失败。
一致性(Consistency):事务修改数据前后,数据库的数据的完整性,仍然保持一致。
隔离性(Isolation):数据库中一个事务,可能被其他事务影响,为了避免这种情况发生,数据库提供了几种隔离级别供用户选择。
持久性(Durability):数据库中的事务一旦提交,就会永久改变,无法改变,即使数据库损坏,也无法恢复原有的数据。
- 数据库中的四大特性
数据库中除了隔离性,另外三种性质都得到了具体实现,开发中直接使用即可,唯独隔离性,没有做出具体实现。原因是:
每一个数据库用户,对于数据库的隔离级别看法不同,数据库不能写死其隔离级别,如果写死的话会很不友好,所以隔离性需要用户自行设置。
考虑数据库的隔离级别,就是分析数据库中事务之间的线程安全问题,所以可以从数据涉及读和写来分析。
两个事务同时读取数据:
这种情况不会存在线程安全问题。因为两个事务对应的线程都未对数据进行修改,任何人读取的数据内容都相同。
两个事务同时写入数据:
这种情况一定会存在线程安全问题。因为两个事务对应的线程都会对数据进行修改,第二次修改可能会覆盖第一次的操作,所以应该将两个事务严格分开。可以通过修改数据库的隔离级别,来保证两个事务分别执行,避免出现线程安全问题。
一个事务写,一个事务读:
在面对一个事务写,一个事务读这种情况时,可以选择修改数据库的隔离级别达到自己想要的效果。如果要求数据库安全性与正确性,则可以选择最高的隔离级别。如果要求数据的写入速度,则可以选择数据库的最低隔离级别。多数情况需要两者兼顾,可以选择中间两个隔离级别。面对数据库中存储的脏读,不可重复读,虚读/幻读这些情况,也可以根据自己的需求,修改数据库的隔离级别。防止这些问题的隔离级别,推荐使用repeatable read。原因是这个级别,不会出现脏读,和不可重复读。而虚读出现的情况极少。可以认为当前隔离级别是安全且能够保证执行效率的。
- 几种事务之间互相影响的情景分析
脏读:一个事务读取到另一个未提交事务的数据,导致数据前后读取不一致。
a:1000 买家
b:1000 商家
--------------------------------------------------------------------
a:start transaction;
update user set money = money -100 where name='a';
update user set money =money +100 where name ='b';
-----------------------------------------------------------------------------
b:start transaction;
select * from user where name ='b';-----1100
-----------------------------------------------
a:rollback;
-------------------------------------------------------
b:select * from user where name ='b';-----1000
不可重复读:一个事务读取到另一个已经提交事务的数据,在事务提交前后,读取的数据内容不一致,这种现象就称之为不可重复读。
------------------------------------------------------------------------
a:1000 1000 1000
------------------------------------------------------------------------
b:start transaction;
select hq from account where name='a';----1000;
select dq from account where name='a';----1000;
select gp from account where name='a';----1000;
-----------------------------------------------------------------
a:start transaction;
update account set hq = hq-100 where name='a';
commit;
------------------------------------------------------------------
b:
select hq+dq+gq from account where name='a';---2900
--------------------------------------------------------------------
hq 1000
dq 1000
gp 1000
sum 2900
虚读/幻读:在整表操作中,一个事务读取到另外一个已经提交事务的数据,导致事务提交前后数据不一致的现象,这种现象称之为虚读/幻读。
-----------------------------------------------------------------------------
a:1000
b:2000
------------------------------------------------------------------------------
d:
start transaction;
select sum(money) from user; ----3000
select count(money) from user; ----2
---------------------------------------------------------------------
c: 3000
start transaction;
insert into user values('c',3000);
commit;
----------------------------------------------------------------------
d:
select avg(money) from user;---- 1500
数据库隔离级别
在数据库的事务特性当中,唯独没有实现隔离性的内容,转而是为用户提供四个隔离界别的选项,用户可以根据自己的需求,选择合适隔离级别。
- 四个隔离级别
read uncommitted; | 脏读,不可重复读,虚读/幻读,都会出现。数据库安全性最低,但是效率最高。 |
read committed; | 可以防止脏读。不可以防止不可重复读及虚读/幻读。数据库安全性较低,效率较高。 |
repeatable read; | 可以防止脏读,和不可重复读。不可以方式虚读/幻读.数据库的安全性较高,效率较低。mysql默认的隔离级别。 |
serializable; | 可以防止脏读,不可重复度和虚读/幻读.它是mysql最高的隔离级别。是一个串行化的处理方式,虽然安全性较高,但是执行效率过低。 |
- 隔离级别的选择
安全:serializable > repeatable read >read committed>read uncommitted
性能:read uncommitted>read committed> repeatable read>serializable由于read uncommitted隔离级别过低,安全性保证较差,所以实际开发中使用较少。serializable隔离级别过高,安全性虽然较高,但是效率极低,对于开发影响较大,所以实际开发中很少使用。
read committed和repeatable read两个隔离级别较为使用,对于数据安全性和语句执行效率都有较好的保证,开始时多数会采用两者其中一个。
修改数据库的事务级别
set global/session transaction isolation level read uncommitted;
set transaction isolation level read uncommitted;临时修改,只对下一次的事务生效
-------------------------------------------------------------------------------------------
查询当前数据库的隔离级别
select @@tx_isoaltion;
数据库中的锁
- 共享锁和排他锁
a. 在非serializable隔离级别之下,查询不加锁。增删改添加排它锁。
b. 在serializable隔离级别之下,查询添加共享锁。增删改添加排它锁。
特点:
i. 共享锁和共享锁可以共存。
ii. 共享锁和排它锁不能共存。
iii. 排它锁和排它锁不能共存。
- 表级锁和行级锁
表级锁:添加锁之后,当前表格,不允许另外的事务操作。
行级锁:添加锁之后,当前表格中的某一行或几行,不允许另外的事务操作。
- 死锁
两个线程相互等待对方释放资源,这种现象称之为死锁。
解决死锁的方案:
i. 销毁其中任意一个线程。
ii. 修改代码。