深入理解分布式事务
本笔记基于书《深入理解分布式事务原理与实战》机械工业出版社出版一书总结。
第一章:分布式事务基础
事务的基本概念
事务的特性
事务的类型
本地事务
Mysql事务基础
事务的特性
- 四大特性:原子性、一致性、隔离性、持久性
原子性:构成事务的所有操作要么全部执行成功,要么全部执行失败。
一致性:事务执行前后,数据始终处于一致的状态。
隔离性:并发执行的两个事务之间互不干扰。
tips:Mysql通过锁和MVCC机制保证事务的隔离性
持久性:此事务对数据的更改操作会被持久化到数据库中,并且不会被回滚。
事务的类型
五大类:扁平事务 带有保存点的扁平事务 链式事务 嵌套事务 分布式事务
-
扁平事务:通常由begin或者start transaction 开始,由commit或者rollback结束。这之间的操作要么全部执行成功,要么全部执行失败。当今主流的数据库都支持扁平式事务。缺点:无法提交或者回滚整个事务中的部分事务。
-
带有保存点的扁平事务:通过在事务内部的某个位置设置保存点,达到将当前事务回滚到此位置的目的。
在mysql中,示例如下:
savepoint save_user_point #设置一个名称为save_user_point的保存点
rollback to save_user_point #将当前事务回滚到定义的保存点位置
release savepoint save_user_point #删除保存点
- 链式事务:链式事务是在带有保存点的扁平事务的基础上,自动将当前事务的上下文隐式地传递给下一个事务。一个事务的提交操作和下一个事务的开始操作具有原子性。
注意:链式事务在提交的时候会释放要提交事务的所有锁和保存点,链式事务的回滚操作,只能回滚到当前事务的保存点,不能回滚到已提交事务的保存点。
- 嵌套事务:最外层有一个顶层事务,这个顶层事务控制着所有的内部子事务,内部子事务提交后,整体事务并不会提交,只有最外层的顶层事务提交完成后,整体事务才算提交完成。mysql不支持原生的嵌套事务。SqlServer支持。
- 分布式事务:分布式事务之的是事务的参与者,事务所在的服务器,涉及的资源服务器以及事务管理器位于不同的分布式系统的不同服务或者节点。
本地事务
也叫传统事务。特征如下:
一次事务过程只能连接一个支持事务的数据库,这里的数据库一般指的是关系型数据库
事务的执行结果必须满足ACID特性
事务的执行过程会用到数据库本身的锁机制
优点:支持的严格的ACID特性,事务可靠,执行效率较高,简单易用。
缺点:不具备分布式事务的能力,一次事务过程只能连接一个支持事务的数据库,即不能用于多个事务性数据库。
MySQL事务基础
并发事务带来的问题
本质上就是两个事务 写写 读写 读写 读写的问题
-
更新丢失(脏写):
A事务和B事务选择同一数据库中的同一行数据,并基于最初选定的值分别进行了更新操作,则后提交的会覆盖掉前提交的。解决办法:让每个事务按照串行的方式执行,按照一定的顺序依次进行写操作
-
脏读:
B事务读取到A事务没有提交的数据,如果A事务进行了回滚,那么B事务读取到的数据就是脏数据。解决办法:先写后读。
-
不可重复读:
A事务不同时刻读取的数据不一致。B事务后来进行了修改或删除。解决办法:先读后写
A读数据 B改数据 A读数据
-
幻读:
一个事务按照相同的条件重新读取之前读过的数据,此时发现其他事务插入了满足当前事务查新条件的新数据,这种现象就叫做幻读。解决办法:先读后写
A读数据 B新增数据 A读数据
tips: 不可重复读和幻读的区别
不可重复读的重点在于更新和删除操作,幻读的重点在于插入操作
使用锁机制实现事务隔离级别时,在可重复读隔离级别中,Sql语句第一次读取的数据,会将相应的数据加锁,其他事务无法修改和删除这些数据,此时可以实现可重复读。
幻读无法通过行级锁来避免,需要使用串行化的事务隔离级别,但是这种事务隔离级别会极大降低数据库的并发能力。
从本质上讲,不可重复读和幻读最大的区别在于如和通过锁机制解决问题。
MysSQL事务隔离级别
注意:隔离级别是针对于事务来说的。
InnoDB默认是可重复读。
事务隔离级别:读未提交 读已提交 可重复读 串行化
配置默认事务隔离级别:
1> 命令行:–transaction-isolation
2> 配置文件:my.cnf 或者my.ini 文件中的mysqld节点修配置如下:
transaction-isolation = READ-UNCOMMITTED/READ-COMMITTED/REPEATABLE-READ/SERIALIZABLE
3> 也可以使用 set transaction 命令单独改变事务的隔离级别
set session/global transaction isolation level READ-UNCOMMITTED/READ-COMMITTED/REPEATABLE-READ/SERIALIZABLE
使用该命令时,不带session或者global,则下一个数据库连接生效
使用global命令,对所有后面新产生的数据库连接生效
使用session命令,对当前的数据库连接设置事务隔离级别,只对当前连接的后续事务生效。
注意区分: 数据库 数据库连接 数据库连接中的事务
MySQL事务隔离级别的区别
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
串行化 | 不可能 | 不可能 | 不可能 |
MySQL中锁的分类
锁是一种协调多个进程或者多个线程都某一资源的访问的机制
mysql使用锁和MVVC机制实现了事务隔离级别
锁的分类:
性能:悲观锁 乐观锁
操作类型:读锁 写锁
数据粒度:表锁 行锁 页锁
更细粒度:间隙锁 临键锁
悲观锁和乐观锁
悲观锁对于数据库中数据的读写持悲观的态度,悲观锁机制下,读取数据时需要加锁,此时不能做修改。修改数据时也需要加锁,其他应用程序无法修改数据。
悲观锁意味着只能进行单一的数据操作,极大的影响性能。
乐观锁对于数据库中的读写持乐观态度。会对数据表中添加一个类似version的版本号,查询数据时,会将版本号一起读出来,更新数据时,将版本号+1,提交数据的版本号大于数据表中当前记录的版本号,则修改,否则不修改。
读锁和写锁
读锁又称共享锁和S锁,同一数据可以加多个读锁而互不影响。
写锁又叫排他锁或X锁,如果当前的写锁未释放,则会阻塞其他的读锁和写锁。
表锁、行锁、页锁
表锁
表锁,在整个数据表上对数据进行加锁和释放锁。特点:开销小,加锁速度快,一般不会出现死锁。
Mysql中,有两种表级锁模式:表共享锁,表独占写锁
当一个线程获取到一个表的读锁后,其他线程仍然可以对表进行读操作,但是不能对表进行写操作。
当一个线程获取到一个表的写锁后,只有持有锁的线程可以对表进行更新操作,其他线程对数据表的读写操作都会被阻塞。
-- 手动增加表锁
lock table 表名称 read(write),表名称2 read(write);
-- 查看某个表的锁
show open tables;
-- 删除表锁
unlock tables;
行锁
行锁在数据行上对数据进行加锁和释放锁。
详细内容可以参考:https://blog.youkuaiyun.com/weixin_52690231/article/details/123483019?ops_request_misc=&request_id=&biz_id=102&utm_term=mysql%E4%B8%AD%E7%9A%84%E8%A1%8C%E9%94%81&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-2-123483019.142v96pc_search_result_base6&spm=1018.2226.3001.4187
特点:开销大,加锁速度慢,可能出现死锁,锁定力度最小,并发度高。
在Innoddb引擎中,有两种类型的行锁,一种是共享锁,一种是排他锁。
共享锁:允许一个事务读取一行数据,但不允许一个事务中对加了共享锁的当前行增加排他锁。
排他锁:只允许当前事务对数据进行增删改查操作,不允许其他事务对增加了排他锁的数据行增加共享锁和排他锁。
- 注意
- 行锁主要加在索引上,对非索引字段设置条件进行更新操作,行锁可能会被变为表锁。
- Innodb的行锁是针对索引加锁,不是针对记录加索,并且加锁的索引不能失效,否则可能变为表锁。
- 锁定某一行时,可以使用lock in share mode 指定共享锁,使用 for update命令指定排他锁。
例如:
select * from account where id = 1 for update
页面锁
页面锁,就是在页面级别对数据进行加锁和释放锁。对数据的加锁开销位于表锁和行锁之间,可能会出现死锁。粒度介于行锁和表锁之间。并发度一般。
锁名称 | 系统开销 | 加锁效率 | 是否会死锁 | 锁定粒度 | 锁冲突 | 并发度 |
---|---|---|---|---|---|---|
表锁 | 小 | 快 | 不会 | 大 | 最高 | 最低 |
行锁 | 大 | 慢 | 会 | 小 | 最低 | 最高 |
页锁 | 中 | 中等 | 会 | 中 | 中等 | 中 |
间隙锁和临键锁
mysql使用范围查询时,如果请求共享锁或者排他锁,Innodb会对所有符合条件的数据锁加锁。如果键值在范围内,而这个范围内不存在记录,则认为此时出现了间隙。Innodb会对这个间隙进行加锁。而这种加锁机制就是间隙锁。
间隙锁只在可重复读事务隔离级别下才会生效。
临键锁:行锁和间隙锁的组合。
死锁的产生和预防、
死锁产生的四个必要条件:
互斥,不可剥夺,请求和保持,循环等待
互斥:在一段时间内,计算机的某个资源 只能被一个进程占用。
不可剥夺:某个进程获得的资源在使用完毕前,不能被其他进程强行夺走。只能由获得资源的进程主动释放。
请求和保持:进程至少获得了一个资源,又要请求其他资源,但请求的资源已经被其他进程占领了。此时进程就会被阻塞,并且不会被释放已经获得的资源。
循环等待:系统中的进程相互等待,同时各自占用的资源又会被下一个进程所请求。
查看死锁的相关信息:
show engine innodb status\G
Innodb中的MVCC原理
在MVCC机制中,每个连接到数据库的读操作,在某个瞬间看到的数据库的一个快照,而写操作的事务提交之前,读操作是看不到这些数据的变化的。
查询操作:Innodb存储引擎只会查找不晚于当前事务版本的数据行。
详细参考:https://zhuanlan.zhihu.com/p/475793340 书中讲的很少。