事务ACID特性
名词 | 解释 | 实现 |
---|---|---|
原子性(Atomicity) | 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 | 事务开始时,设置begin;完成后,执行commit;失败时,执行rollback;注意:commit和rollback只会执行其一。 |
一致性(Consistency) | 一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。 | |
隔离性(Isolation) | 隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。 | 隔离级别:read-uncommitted;read-committed;repeatable-read;Serializable |
持久性(Durability) | 在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中(写硬盘了),并不会被回滚。 | WAL |
隔离级别
查找数据当前隔离级别:show variables like "%isolation%";
MySQL 默认的隔离级别是:REPEATABLE-READ
Oracle、SQL Server,它们的默认隔离级别也是REPEATABLE-READ吗?
Oracle 默认使用的是READ-COMMITTED
READ-UNCOMMITTED:未提交读
READ-COMMITTED:提交读
REPEATABLE-READ:可重复读
Serializable:串行化
事务并发执行时,到底会有哪些影响?
脏读,读取了其他事务未提交的值,而这些未提交的值可能是无效的值(例如被回滚了)
不可重复读,即两次读取的数据发生了变化;
幻读,即两次读取数据时,产生了之前不存在的数据;
数据读写
写锁
在事务内,对该行的写操作会加写锁,此时其他事务,无法对该行做修改,除非当前事务提交/回滚,锁被释放。
写锁可以解决:
临界资源写冲突;
修改丢失;
读
读会加锁吗?
读到底发生了什么?
快照读
快照读:
数据拥有很多的版本;
MVCC:多版本并发控制协议;
读取了其中的某一个版本,版本跟隔离级别有关;
哪些是快照读?比如:select * from account;
好处:快,支持高并发。
MVCC(多版本并发控制协议)
repeatable-read隔离级别下
记录数据在读取事务开始时刻,已提交的(未删除的)的版本;
在整个事务中,只会读取该版本的数据,任何修改更新都不会影响;
read-committed隔离级别下
数据读取的时刻,已提交的(未删除)的数据;
read-uncommitted隔离级别下:
没有版本的概念,所有数据都会更新到数据库,只用读取最新的数据即可。
serializable
没有版本的概念,读也会加锁。
当前读
当前读:只会读取当前最新的值。
示例:
update account set money = money - 50 where id = 1;
以及所有的修改命令,
再加上:
select * from account where … lock in share mode:读取数据,并加共享锁。
select * from account where … for update:读取数据,并加排它锁/互斥锁。
间隙锁(Mysql的Repeatable-Read解决幻读)
id | username | password | money | role |
---|---|---|---|---|
1 | admin | admin | 0.00 | admin |
2 | huawei | hw6666 | 5999.00 | |
3 | redmi | k30 | 1999.00 | |
5 | oppo | opporeno | 3299.00 | |
10 | vivo | vivox30 | 8848.00 | |
12 | mi | mialpha | 8848.00 | |
13 | huawei | hw11111 | 5999.00 | |
14 | oppo | ace3000 | 2999.00 |
示例1:
事务A:
update account set money=1111 where id>7;
事务B:
insert into account (id,username,password,money) values(8,"mi","mimix3",3499);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
解释:(5,10),[10,14],(14,+无穷)加间隙锁,不能insert into
update account set money=999 where id=13;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
解释:id>7的[10,14]加行锁,不能update
Read-committed无间隙锁,性能较好。
示例2:
事务A:
update account set money=999 where role="admin";
事务B:
update account set money=999 where id=10;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into account (id,username,password,money) values(8,"mi","mimix3",3499);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
解释:role不是索引,在用它update时,会锁表(行锁+间隙锁都开启),update和insert都不能成功;
使用注解时的配置类示例
package com.myspring.day8.myConfig;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@EnableTransactionManagement
@Configuration
@PropertySource("jdbc.properties")
@ComponentScan("com.myspring.day8")
public class BookConfig {
@Value("${jdbcUrl}")
private String jdbcurl;
@Value("${myname}")
private String username;
@Value("${password}")
private String password;
@Value("${driverClass}")
private String driverClass;
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource dataSource=new DruidDataSource();
dataSource.setUrl(jdbcurl);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClass);
return dataSource;
}
@Bean("transactionManager")
public PlatformTransactionManager getTransactionManager(DataSource dataSource) {
DataSourceTransactionManager manager = new DataSourceTransactionManager(dataSource);
// 禁止内部事务在出错时设置“rollback-only”
// manager.setGlobalRollbackOnParticipationFailure(false);
return manager;
}
@Bean("jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}