发布时间:2021-08-26 09:12:20 阅读次数:224
事务
事务支持ACID特性
A原子性:所有操作要么都做要么都不做
C一致性:事务将数据库从一种状态变为另一种状态一致性,保证数据库完整性约束,例如唯一索引约束等
I隔离性:事务与事务之间是不可见的
D持久性:事务一旦提交那么事务就是永久性的
ANSI/ISO SQL标准定义了4中事务隔离级别:未提交读(read uncommitted),读提交(read committed),可重复读(repeatable read),串行读(serializable)。
对于不同的事务,采用不同的隔离级别分别有不同的结果。不同的隔离级别有不同的现象。主要有下面3种:
1、脏读(dirty read):一个事务可以读取另一个尚未提交事务的修改数据。
2、不可重复读(nonrepeatable read):在同一个事务中,如果数据被其他事务修改,不能重复读取该记录原始值。
3、幻像读(phantom read):在同一事务中,同一查询多次进行时候,由于其他插入操作(insert)的事务提交,导致每次返回不同的结果集。
不同的隔离级别会产生不同的现象,4种事务隔离级别分别表现的现象如下表:
隔离级别 脏读 非重复读 幻像读
read uncommitted 允许 允许 允许
read committed 不允许 允许 允许
repeatable read 不允许 不允许 允许
serializable 不允许 不允许 不允许
注意:
1.read committed:不可以杜绝幻象读.
2.serializable: 当没有开启autocommit,InnoDB会将select转换为 SELECT ... LOCK IN SHARE MODE(加S锁).如果开启autocommit,那么和普通事务中的select一样为一致性非锁定读.
实现方式
redo日志:当事务提交时写入重做日志文件,与二进制日志不同的是,重做日志是InnoDB存储引擎设置的,二进制日志是MySQL层的任何存储引擎都会产生二进制日志.其次,重做日志是物理日志,记录着页修改信息,二进制是逻辑日志.
redo日志可以配置重做日志缓存实现,先写缓存然后再通过磁盘同步策略写入磁盘(CheckPoint,文件缓冲).
LSN:log sequence number日志序列号,重做日志写入文件的序列号.三个作用:重做日志写入总量,Checkpoint的位置,页的版本.
undo日志:记录之前版本记录的日志.是逻辑日志,记录每一个操作会执行一个相反的操作.例如:一个INSERT操作会执行一个DELETE操作.MVCC机制就是通过undo日志实现.undo页为了提升磁盘空间利用效率,采用了循环写文件形式节省磁盘空间,对于标记可重用的undo页会分配给下个事务.
当事务提交时,将undo日志放入链表(以记录进行组织),链表可以管理undo日志用于MVCC以及空间释放,undo页可能存放着不同的事务的undo日志.
如果长时间存在未提交的事务,那么会导致undo日志空间暴涨.
事务实践
begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作
InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。
更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。
在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。
分布式事务
分布式事务指的是允许多个独立的事务资源参与到一个全局的事务中.
相对于一阶段提交,两阶段提交的第一阶段是为第二阶段是提交还是回滚提供判断.一阶段提交无法完成两阶段的处理,当某些参与者的节点无法完成处理时,这时会出现一些节点已经提交,一些需要回滚,但是已经提交的缺不能回滚,破坏了事务的原子特性.
分布式当中需要一个特殊的东西就是协调器,协调不同资源管理器(一般指数据库)之间的事务处理.
分布式使用两段式提交:
1.第一阶段(投票阶段):协调器向所有参与全局事务的节点都询问准备状态,各节点将要提交的数据写入undo与redo日志,各参与者返回状态同意将进入下一阶段,否则返回中止.
2.第二阶段(完成阶段):如果第一阶段任何一个节点返回中止所有的节点都需要回滚,事务管理器告诉资源管理器执行ROLLBACK,如果是同意执行COMMIT.
两阶段提交前提条件:
每个节点都有稳定的存储,每个节点都预写日志,日志中的数据永远不会丢失.并且每个节点永远不会down掉,并且任意两个节点可以通讯.
两阶段提交最大缺点是阻塞协议,当一协调器Down掉那么,资源管理器会等待协调器的ROLLBACK或COMMIT.
两阶段提交的前提条件过于严苛,当然我们可以通过一些监控,高可用方案来保证事务的最终一致性.
分布式事务的隔离级别须为SERIAlIZABLE.读锁为分布式事务的数据一致性提供了可靠保障.
在MySQL中不同的版本对XA事务对复制有一些限制(MySQL服务中断,XA事务可能会导致Binary log主从不一致等)使用时需要额外注意.
锁
锁并不是和事务绑定一起的,不同的语句可能锁集不同,但是事务的隔离级别会影响锁的范围.
不在事务当中的UPDATE,INSERT等也都会用到锁.
锁的范围
从范围可以将锁大致分为三种(mysql的官方问档也介绍了其他的锁):
1.record lock:记录锁,当我们加入select * from orders whereid = 1 for update这样会阻止其他语句增删改这条数据.
2.gap lock:间隙锁,这个锁会锁住该数据上一个邻近位置(根据索引或者主键)与下一个邻近位置之间的范围.(对于索引而言)
针对主键或者是唯一索引gap lock会被优化为记录锁.可以理解下因为他们都是唯一的不需要间隙锁来防止幻读发生.
锁住区间范围(邻近的index-1,index);
注意: 根据索引的排序大于index的第一个索引以及小于index的第一个索引(不仅仅是针对数字类型,字符串按照字符串的比较的方式),邻近区间范围结合B+tree的特性,索引的链表去思考.
3.next-key lock:gap lock + recordlock
事务级别最低为repeatable read,会启用这个锁,如果低于这个级别会不起用这个锁.
因此锁的范围就变为了(邻近的index-1,index];
邻近的index+1不是真正的record lock,只是锁住了最大索引值的间隙,目的是为了避免幻读.
锁的问题只有我们在select语句当中明显的加入,FOR UPDATE语句才可以明显的指出这个这个事务或者会话当中采用锁.
将事务改为READ COMMITED,就会关闭gap lock.
对于事务REPEATABLE READ 默认开启next-key lock.
REPEATABLE READ下的加锁规则
原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
原则 2:查找过程中访问到的对象才会加锁。
优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,nextkeylock 退化为间隙锁。
一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
读写锁
共享锁:允许获取到锁的事务去读取这一行.
共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
A shared (S) lock permits the transaction that holds the lock to read a row.
An exclusive (X) lock permits the transaction that holds the lock to update or delete a row.
排他锁:允许获取到事务的锁更新删除这一行.
排他锁又称写锁、独占锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
事务中对于 FOR UPDATE有以下几点需要注意:
1.凡是用explain显示为ALL或者index,range会导致锁住的记录过多影响并发.
2.减少事务中的执行的语句
3.只有当执行事务提交后,fo updater的语句对应的锁才会被释放.
针对死锁的一些优化
1.在delete,update只采用逐渐或者唯一键,我们可以将数据库的数据类型改为reade commited,降低锁的范围.
2.不同事务间访问表的顺序尽量一致.
一致性非锁定读
一致性非锁定读:通过行多版本控制的方式来读取当前执行时间数据库中行的数据.
当一行被加入X锁,那么InnoDB会根据当前事务的隔离级别读取对应的Undo日志.
Read Commited:每次读取最新的Undo日志.
Repeatable Read:每次都读取事务开始时的Undo数据.
一致性锁定读
SELECT ... FOR UPDATE
SELECT ... IN SHARE MODE
1
2
对当前的读取的数据加锁,但是对于一致性非锁定读还是可以正常进行读取数据.
锁相关的表
INNODB_TRX 主要字段介绍:
字段 说明
trx_id 内部唯一事务ID
trx_state 当前事务状态
trx_started 事务开始时间
trx_requested_lock_id 事务等待锁的的ID
trx_wait_started 事务等待开始的时间
trx_weight 事务权重
trx_mysql_thread_d 线程ID
trx_query 事务运行的SQL语句
trx_rows_locked 锁住行数近似数
trx_adaptive_hash_latched 自适应哈希索引是否被当前的事务锁住
INNODB_LOCKS
字段名 说明
lock_id 锁ID
lock_trx_id 事务ID
lock_mode 锁的模式
lock_table 要加锁的表
lock_index 锁住的索引
lock_space 锁住对象的space_id
lock_page 事务锁定页数量
lock_rec 事务锁定行数量
lock_data 事务锁定记录的主键值
为表锁时,lock_page,lock_rec,lock_data这几个值为NULL.
INNODB_LOCK_WAITS:
字段 说明
request_trx_id 申请锁资源的事务ID
request_lock_id 申请的锁的ID
blocking_trx_id 阻塞的事务ID
blocking_lock_id 阻塞锁的ID
通过这几张表可以查询当前锁等待的情况.
死锁
死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待.
等待图(wait-for-graph)可以用来检测死锁,通过深度优先算法实现,只要图中存在循环的回路.那么存在死锁.
解决死锁时会回滚当中undo量最小的一个事务.
意向锁
意向锁是表级锁,指明事务在将要对表中的行加哪种意向锁.
IS:指明事务将会在表中的行加共享锁.
IX:指明事务将会在表中的行加排它锁.
意向锁的意义:表明某个事务正在锁或者将要锁表中的一行数据.
意向锁不会阻塞除全表操作以外的(例如:LOCK TABLES … WRITE)任何请求
如果一个事务想在表中行加S锁,那么必须先获取IS锁
如果一个事务想在表中行加X锁,那么必须先获取IX锁
X IX S IS
X Conflict Conflict Conflict Conflict
IX Conflict Compatible Conflict Compatible
S Conflict Conflict Compatible Compatible
IS Conflict Compatible Compatible Compatible
假设一个事务要更新整个表(ALTER TABLE),这事会有个X表锁,X表锁会和当前所有事务的意向锁(也是表级锁)进行兼容性检测,意向锁和表级锁检测是很快的,但是检测表级锁和行级锁是比较困难的.
可见意向锁主要是为了方便实现与表级锁检测判断,提升性能.
MDL锁(Meta Data Lock)
MDL是意向锁的实现,这个锁是表级锁,MDL不需要显式使用,在访问一个表的时候会被自动加上。DML会加一个读锁。
我们经常遇到线上由于业务需要加字段,这个是个比较危险的操作,我们一般选择在业务的低峰期执行。
DDL的操作流程:
拿MDL写(X)锁
降级成MDL读(S)锁
真正做DDL
升级成MDL写(X)锁
释放MDL锁
如果DDL拿不到MDL写锁,会一直等待,并阻塞在其之后的DML。