事务
事务逻辑上一组操作,要么都执行,要么都不执行,是最小的不可再分的工作单元。事务可由一条简单的SQL语句组成,也可以由一组复杂的SQL语句组成。
事务特性:ACID
- 原子性(Atomicity):事务是最小单位,不可再分,要么全部成功,要么全部失败。
- 一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
- 隔离性(Isolation):并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。
- 持久性(Durability):事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。
并发事务异常
在事务的ACID特性中,C即一致性是事务的根本追求,而对数据的一致性破坏主要来自两方面:
- 事务的并发执行
- 事务故障或系统故障
导致的异常有:
- 脏读(Dirty read):指一个事务读取了另一个事务未提交的数据。当事务A正在访问数据并且对数据进行了修改,但还没提交到数据库中,这时另外一个事务B也访问了这个数据,并使用了这个数据。因为这个数据还没提交,那么事务B读到的数据是“脏数据”。
- 丢失修改(Lost to modify):指事务覆盖了其他事务对数据的已提交修改。事务A读取并修改一个数据时,事务B也访问并修改了该数据,覆盖了事务A修改的数据,导致事务A修改的数据丢失。
- 不可重复读(Unrepeatable read):指一个事务对同一数据的读取结果前后不一致。在一个事务内多次读取同一数据,期间另外一个事务对该数据进行了修改,导致第一个事务前后读取的不一致。
- 幻读(Phantom read):幻读是指事务读取某个范围的数据时,因为其他事务的操作导致前后两次读取的结果不一致。幻读与不可重复读类似,但不可重复读的重点是修改,而幻读的重点在与新增或删除。
事务隔离级别
SQL 标准定义了四个隔离级别,从低到高依次是:
-
READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能或导致脏读、幻读、不可重复读。
-
READ-COMMITTED(读取已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读、不可重复读仍有可能发生。
-
REPEATABLE-READ(可重复读):对同一数据的多次读取结果都是一致的,除非数据已经被本身事务所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
-
SERIALIZABLE(可串行化):最高隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。可以阻止脏读、不可重复读和幻读。
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
Read uncommitted | 可能 | 可能 | 可能 |
Read committed | 不可能 | 可能 | 可能 |
Repeatable read | 不可能 | 不可能 | 可能 |
Serializable | 不可能 | 不可能 | 不可能 |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。
这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。
乐观锁和悲观锁
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。
传统的关系型数据库就用到了很多这种锁机制,如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。
Java中Synchronized和ReentrantLock等独占锁都是悲观锁的实现。
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
实现方法
-
版本号机制
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
-
CAS算法
CAS:Compare and swap(比较和交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数:
- 需要读写的内存值 V
- 进行比较的值 A
- 拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
使用场景
- 乐观锁适用于读多写少的情况。即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。
- 悲观锁适用于多写的场景,或对系统一致性要求高的场景。
分布式事务
分布式事务是指允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是指关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚。
InnoDB提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。
在使用分布式事务时,InnoDB的事务隔离级别必须设置为SERIALIZABLE。
XA事务
XA事务由一个或多个资源管理器(Resource Managers)、一个事务管理器(Transaction Manager)以及一个应用程序(Application Program)组成。
- 资源管理器:提供访问事务资源的方法。通常一个数据库就是一个资源管理器。
- 事务管理器:协调参与全局事务中的各个事务。需要和参与全局事务的所有资源管理器通信。
- 应用程序:定义事务的边界,指定全局事务中的操作。
在MySQL数据库的分布式事务中,资源管理器就是MySQL数据库,事务管理器为连接MySQL服务器的客户端。
两段式提交
分布式事务使用两段式提交(two-phase commit)的方法。
- 第一阶段,所有参与全局事务的节点都开始准备(PREPARE),告诉事务管理器它们准备好提交了。
- 第二阶段,事务管理器告诉资源管理器都执行ROLLBACK还是COMMIT。
如果任何一个节点显示不能提交,则所有的节点都被告知需要回滚。
资料引用
https://tech.meituan.com/2014/08/20/innodb-lock.html
https://baijiahao.baidu.com/s?id=1638043442119382144&wfr=spider&for=pc