分布式事务
由于整个系统涉及到很多服务 不同服务之间可能会对共享的数据并发的修改 导致数据的不一致性
分布式事务主要讨论的问题有:
1.多进程并发修改共享数据如何保证数据一致性
2.不同的请求之间数据如何进行隔离
3.一个请求可能由多个服务共同完成,如何保证这些服务所有的操作要么同时成功要么同时失败?
4.一个请求如果由三个服务共同完成,如果前两个服务成功了,最后一个服务失败了,如何保证数据的回滚?
在单服务的场景下,大多数场景下只需要增加@Transaction的注解
但是在分布式环境下,往往是多个进程对共享数据进行并发修改.
什么是事务
事务是一种可靠,一致的方式,是访问和操作数据库中的数据单元.
数据库中的事务:把多条SQL语句看成是一个整体的任务,这个整体的任务要么是语句全部执行成功,要么就是全部执行失败.
(ACID)特性
原子性(Atomicity):
事务就是一个不可以分割的工作单位,事务中的操作要么全部成功,要么全部失败.
比如:Batman转账给Superman的过程,一开始Batman有100元,Superman有100元,
Batman转账100元给Superman分为两个动作,分别是Batman扣除100元和Superman增加100元,
这两个动作要么共同发生,要么全部失败,体现的就是原子性.
一致性(Consistency):
事务必须使数据库从一个一致性状态变换为另外一个一致性状态.
比如:Batman和Superman各100元,转账之前加起来200元,转账之后两者账户加起来还是200元.
隔离性(Isolation):
事务的隔离性是指多个用户并发访问数据库的时候,数据库为每一个用户开启一个事务,不能被其他的事务干扰,多个并发事务之间要相互的隔离.
比如:Batman和Superman各100元,在Batman给Superman转账的过程中,Superman去查询自己的账户,此时产生两个事务,显示的金额体现了事务的隔离级别.
持久性(Dependence):
持久性就是指一旦一个事务被提交了之后,它对数据库的改变就是永久的,接下来即使是数据库发生了故障也不能够对这个事务有任何的影响.
比如:Batman转账100元给Superman,转账事务结束后,数据库即使发生了故障,也无法对该转账事务结果产生影响.
事务的隔离级别
名字 隔离级别 脏读 不可重复读 幻读 数据库默认
可序列化 Serializable 否 否 否 (等级最高 但占用系统资源更多)
可重复读 Repeatable Read 否 否 是 MySQL
读提交 Read Committed 否 是 是 Oracle 和 SQL Server
读未提交 Read Uncommited 是 是 是
脏读 : 就是一个事务读到了另外一个未提交的数据.
幻读 : 在你开启的事务中,读到了别的事务提交的新增插入的数据.
不可重复读:不可重复读是重复读取了另外一个事务已经提交了的数据,当你在一个事务中读两次,同时别的事务改变了数据并提交了事务,你两次读到的数据就不一致.
可重复读:在你的事务中,读到的数据以你第一次读到的为准,即使别的事务在你读到数据之后进行修改了并提交了,你也不会读到已经提交的数据.
之所以关注事务之间的隔离级别本质是因为在高并发情况下,多个线程或是进程会操作同一个数据库中的共享数据,此时有多个事务存在,此时数据库的隔离级别影响到了事务并发操作数据,
最安全的隔离级别是可序列化,在这个隔离级别下,所有的事务都是线性的,而不是并行的,就像是<使用Zookeeper实现分布式锁>中所介绍的悲观死锁一样.但是性能最差.在Mysql中我们可以使用for update为表或是表中的行加锁.
性能最好的事务隔离级别就是读未提交,但是这种事务隔离级别造成共享数据不安全性,也就是线程不安全的,在高并发下违背事务的一致性.
MySQL事务操作
MySql的事务隔离等级:可重复读
MySql中的锁机制
mysql中的锁根据级别划分为读锁和写锁
其中X锁也可以用于分布式锁的应用,但是高并发的X锁极大影响到了查询操作,非常影响性能和用户体验,所以不建议使用
1). 读锁(共享锁/S锁)
当某个事务对这些数据加了读锁后,其他的事务只能对这些数据加读锁,也就是只能读取这些数据.
2). 写锁(排他锁/X锁)
写锁的作用是某个事务对数据加了写锁之后,其他事务不能对这些数据加任何锁。
借助JDBC处理大数据与事务关联(用若干对象模拟大数据)
借助JDBC处理大数据与事务关联(用若干对象模拟大数据)
1.准备Dept类
public class Dept {
private int id;
private String uname;
private String upwd;
public Dept(int id, String uname, String upwd) {
super();
this.id = id;
this.uname = uname;
this.upwd = upwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUpwd() {
return upwd;
}
public void setUpwd(String upwd) {
this.upwd = upwd;
}
}
2.测试类
public class BigDataTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
String constr = "jdbc:mysql://localhost:3306/mybatis0202";
String uname = "root";
String upwd = "zzj6660534";
Connection conn = null;
Statement stmt = null;
Dept d1 = new Dept(1, "lisi", "123");
Dept d2 = new Dept(2, "lisi", "123");
Dept d3 = new Dept(3, "lisi", "123");
Dept d4 = new Dept(4, "lisi", "123");
Dept d5 = new Dept(5, "lisi", "123");
Dept d6 = new Dept(6, "lisi", "123");
Dept d7 = new Dept(7, "lisi", "123");
Dept d8 = new Dept(8, "lisi", "123");
Dept d9 = new Dept(9, "lisi", "123");
Dept d10 = new Dept(10, "lisi", "123");
Dept d11 = new Dept(11, "lisi", "123");
List<Dept> depts = new ArrayList<Dept>();
depts.add(d1);
depts.add(d2);
depts.add(d3);
depts.add(d4);
depts.add(d5);
depts.add(d6);
depts.add(d7);
depts.add(d8);
depts.add(d9);
depts.add(d10);
depts.add(d11);
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(constr, uname, upwd);
stmt = conn.createStatement();
conn.setAutoCommit(false);
int beatchNum = 0;//计数器
for(int i = 0; i<depts.size(); i++) {
//缓存不提交
stmt.addBatch("insert into dept(id,uname,upwd) values("+depts.get(i).getId()+",'"+depts.get(i).getUname()+"','"+depts.get(i).getUpwd()+"')");
beatchNum += 1;
//如果被5整除进行提交缓存(每五条进行提交缓存)
if(beatchNum % 5 == 0) {
//执行缓存区所有语句
stmt.executeBatch();
conn.commit();
System.out.println("提交");
}
}
stmt.executeBatch();
conn.commit();
System.out.println("全部提交");
stmt.close();
conn.close();
}
}