Mysql存储引擎
Mysql的存储引擎包括:MyISAM、InnoDB、BDB、MEMORY、MERGE、EXAMPLE、NDBCluster、ARCHIVE、CSV、BLACKHOLE、FEDERATED等,其中InnoDB和BDB提供事务安全性,其他存储引擎都是非事务安全性。
最常使用的2种存储引擎:
1.在MySQL 5.5之前,当您创建表而未明确指定存储引擎时,MyISAM是默认存储引擎。从版本5.5开始,MySQL使用InnoDB作为默认存储引擎。每个MyISAM在磁盘上存储成三个文件。文件名都和表名相同,扩展名分别是.frm(存储表定义)、.MYD(MYData,存储数据)、.MYI(MYIndex,存储索引)。数据文件和索引文件可以放置在不同的目录,平均分布io,获得更快的速度。
2.InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比Myisam的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索。
Mysql事务和Spring事务
一想到事务就是spring的事务,但是实际上Spring事务本质是对数据库事务的支持,如果数据库不支持事务(例如MySQL的MyISAM引擎不支持事务),则Spring事务也不会生效。
sql实现事务
在说spring的事务之前,我想让大家回忆下,直接写sql语句是怎么实现事务来着?
//设置为手动提交,或者START TRANSACTION;都能开启事务
set autocommit=0;
select * from t_user where `name`='xxx';
update t_user set `password`='666' where `name`='xxx';
COMMIT;
可见由三步组成:
- 开启事务
- 业务sql执行
- 提交/回滚事务
我们来看下不用spring的情况下完成事务的实现。
public void doTest(String arg) {
Connection conn = null;
PreparedStatement ps = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "123456");
//开启事务
String sql = "set autocommit=0";
ps = conn.prepareStatement(sql);
ps.execute();
sql = "update t_student set name = ?,age = ? where id = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, "小明");
ps.setInt(2, 20);
ps.setInt(3, 2);
ps.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
if (conn != null){
try {
//回滚事务
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
} finally {
//释放资源
}
}
mysql默认的事务隔离级别:REPEATABLE-READ(select @@tx_isolation)
脏读:一个事务对数据进行了增删改查,但是未提交事务。另一个事物可以读取到未提交的数据,如果第一个事务进行了回滚,那么第二个事务就读到了脏数据。
例子:领导给张三发工资,10000元已打到张三账户,但该事务还未提交,正好这时候张三去查询工资,发现10000元已到账。这时领导发现张三工资算多了5000元,于是回滚了事务,修改了金额后将事务提交。最后张三实际到账的只有5000元。
不可重复读:一次事务发生了两次读操作,两个读操作之间发生了另一个事务对数据修改操作,这时候第一次和第二次读到的数据不一致。不可重复度关注点在数据更新和删除,通过行级锁可以实现可重复读的隔离级别。
例子:张三需要转正1000元,系统读到卡余额有2000元,此时张三老婆正好需要转正2000元,并且在张三提交事务前把2000元转走了,当张三提交转账是系统提示余额不足。
幻读:指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。
相对于不可重复读,幻读更关注其它事务的新增数据。通过行级锁可以避免不可重复读,但无法解决幻读的问题,想要解决幻读,只能通过Serializable隔离级别来实现。
Spring 声明式注解事务
使用声明式注解事务是比较简单的方式,将事务管理器交予 Spring 管理,在目标类或者目标方法上添加注解 @Transactional 即可同Propagation.REQUIRED
类型 | 名称 | 描述 |
事务传播行为 Propagation | REQUIRED | 当前如果有事务,Spring就会使用该事务;否则会开始一个新事务;默认选择 |
SUPPORTS | 当前如果有事务,Spring就会使用该事务;否则不会开始一个新事务 | |
MANDATORY | 当前如果有事务,Spring就会使用该事务;否则会抛出异常 | |
REQUIRES_NEW | Spring总是开始一个新事务。如果当前有事务,则该事务挂起 | |
NOT_SUPPORTED | Spring不会执行事务中的代码。代码总是在非事务环境下执行,如果当前有事务,则该事务挂起 | |
NEVER | 即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常 | |
NESTED | 如果当前有事务,则在嵌套事务中执行。如果没有,那么执行情况与Transaction- Definition.PROPAGATION_REQUIRED一样 | |
事务的隔离级别 Isolation | DEFAULT | 默认隔离级别(多数数据库默认 ) |
READ_UNCOMMITTED | 对应数据库 Read Uncommitted;产生 脏读、不可重复读、幻读 | |
READ_COMMITTED | 对应数据库 Read Committed;避免 脏读,产生不可重复读、幻读 | |
READ_COMMITTED | 对应数据库 Read Committed;避免 脏读,产生不可重复读、幻读 | |
REPEATABLE_READ | 对应数据库 Repeatable Read;避免 脏读、不可重复读,产生 幻读 | |
SERIALIZABLE | 对应数据库 Serializable,隔离级别最高 |
Spring事务失效场景
访问权限问题
众所周知,java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。
但如果我们在开发过程中,把有某些事务方法,定义了错误的访问权限,就会导致事务功能出问题,例如: