事务:
处理操作性大,复杂性高的数据;比如银行转账,从A账户转到B账户,要确保A账户的钱扣了,B账户上的钱加上了,这就是一个事务;
事务满足的四个条件(ACID):
原子性:一个事务中的过程要么全部完成,要么全部不完成;事务在执行过程中,遇到错误会回滚到事务开始的状态,就像这个失误没执行一样;
一致性:在事务开始之前和结束之后数据的完整性没有被破坏;比如从A账户转出100,B账户到账必须也是100;
隔离性:数据库允许多并发事务同时对数据库的读写,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
持久性:事务处理结束后,数据库的修改是永久的,及时系统故障也不会丢失;
事务的控制语句:
-
BEGIN或START TRANSACTION;显式地开启一个事务;
-
COMMIT;也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改成为永久性的;
-
ROLLBACK;有可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
-
SAVEPOINT identifier;SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;
-
RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
-
ROLLBACK TO identifier;把事务回滚到标记点;
-
SET TRANSACTION;用来设置事务的隔离级别。InnoDB存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。
测试事务demo:
@Autowired
private MemMembersDao mDao;
@Autowired
private DataSourceTransactionManager txManager;
@Test
public void test3(){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();//默认事务定义
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 事物隔离级别,开启新事务
TransactionStatus status = txManager.getTransaction(def); // 获得事务状态
MemMembers m=mDao.selectByPrimaryKey("580139F067894F8293F3356BA6C02DC9");//查询数据库的值
System.out.println("m.getMemWxUnionid():"+m.getMemWxUnionid());
m.setMemWxUnionid("123456");//修改数据库的值
int a=mDao.updateByPrimaryKey(m);
MemMembers mm=mDao.selectByPrimaryKey("580139F067894F8293F3356BA6C02DC9");//查询修改后的数据库的值
System.out.println("mm.getMemWxUnionid():"+mm.getMemWxUnionid());
txManager.rollback(status);//事务回滚
MemMembers m3=mDao.selectByPrimaryKey("580139F067894F8293F3356BA6C02DC9");//再次查询数据的值
System.out.println("m3.getMemWxUnionid():"+m3.getMemWxUnionid());
}
输出结果为:
在我们的正常业务中使用时我们只需做一小小的改动
@Test
public void test3(){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();//默认事务定义
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 事物隔离级别,开启新事务
TransactionStatus status = txManager.getTransaction(def); // 获得事务状态
MemMembers m=mDao.selectByPrimaryKey("580139F067894F8293F3356BA6C02DC9");//查询数据库的值
System.out.println("m.getMemWxUnionid():"+m.getMemWxUnionid());
m.setMemWxUnionid("123456");//修改数据库的值
int a=mDao.updateByPrimaryKey(m);
MemMembers mm=mDao.selectByPrimaryKey("580139F067894F8293F3356BA6C02DC9");//查询修改后的数据库的值
System.out.println("mm.getMemWxUnionid():"+mm.getMemWxUnionid());
try {
//编写业务逻辑
txManager.commit(status);//业务逻辑正常,执行commit
} catch (Exception e) {
//业务逻辑执行失败,执行事务的rollback回滚事件
txManager.rollback(status);//事务回滚
}
MemMembers m3=mDao.selectByPrimaryKey("580139F067894F8293F3356BA6C02DC9");//再次查询数据的值
System.out.println("m3.getMemWxUnionid():"+m3.getMemWxUnionid());
}
说到这里,我在延伸的说下,数据库并发事务引起的问题(脏读,幻度,不可重复读):
一个数据库存在多个访问客户端,这些客户端并发访问数据库时,如果没有采取必要的隔离措施会出现这些问题,这些问题分为5类(包括3类数据读问题:脏读、不可重复读和幻读。两类数据更新问题:第一类丢失更新、第二类丢失更新)。
1、脏读:A事务读取B事务未提交的数据,并在这基础上修改数据时,B事务回滚,那么A事务读取的数据时不被承认的,例如取款和转账业务:
2、不可重复读:A事务读取了B事务已经更改的数据;例如小红查询账户有100块,隔壁老王给小红账户存1000,小红再查时账户余额成1100了,两次查询的结果不一样;
3、幻读:A事务读取了B事务新增的数据;例如:小红在查询公司人员名单时,老王新添加了新入职的小黑进来,小红就会看到突然多出的一条数据,初涉人事的小红还以为自己产生了幻觉,殊不知是幻读。
4、第一类丢失更新:A事务撤销时,覆盖了B事务提交的数据,导致B事务的数据丢失:
5、第二类对视更新:A事务覆盖B事务已经提交的数据,造成B事务所做的操作丢失 :
demo:
public void test3(){
DefaultTransactionDefinition nef = new DefaultTransactionDefinition();//默认事务定义
nef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 事物隔离级别,开启新事务
TransactionStatus status1 = txManager.getTransaction(nef); // 获得事务状态
DefaultTransactionDefinition def = new DefaultTransactionDefinition();//默认事务定义
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 事物隔离级别,开启新事务
TransactionStatus status = txManager.getTransaction(def); // 获得事务状态
Walletuser m2=wDao.selectByPrimaryKey("2");//查询数据库的值
System.out.println("A事务的钱数:"+m2.getFuBao());
BigDecimal d=m2.getFuBao();
Walletuser m=wDao.selectByPrimaryKey("2");//查询数据库的值
System.out.println("B事务的钱数:"+m.getFuBao());
BigDecimal b=m.getFuBao();
BigDecimal c=BigDecimal.valueOf(1000);
BigDecimal dy=b.add(c);
m.setFuBao(dy);//修改数据库的值
wDao.updateByPrimaryKey(m);
txManager.commit(status);//业务逻辑正常,执行commit
Walletuser m4=wDao.selectByPrimaryKey("2");//查询数据库的值
System.out.println("B事务提交后的钱数:"+m4.getFuBao());
BigDecimal de=d.subtract(c);
m2.setFuBao(de);
wDao.updateByPrimaryKey(m2);
txManager.commit(status1);//业务逻辑正常,执行commit
Walletuser m3=wDao.selectByPrimaryKey("2");//查询数据库的值
System.out.println("A事务提交后的钱数:"+m3.getFuBao());
}
输出结果:
为了解决上述问题,数据库通过锁机制解决并发访问的问题。根据锁定对象不同:分为行级锁和表级锁;根据并发事务锁定的关系上看:分为共享锁定和独占锁定,共享锁定会防止独占锁定但允许其他的共享锁定。而独占锁定既防止共享锁定也防止其他独占锁定。为了更改数据,数据库必须在进行更改的行上施加行独占锁定,insert、update、delete和selsct for update语句都会隐式采用必要的行锁定。
但是直接使用锁机制管理是很复杂的,基于锁机制,数据库给用户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的sql语句然后自动选择合适的锁。
不同的隔离级别对并发问题的解决情况如图: