MySQL 事务

数据库事务:

  • 是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作
  • 这些操作作为一个整体一起向系统提交,要么都执行,要么都不执行
  • 事务是一组不可再分割的操作集合(工作逻辑单元)

什么是ACID:

  • 原子性(atomicity): 事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
  • 一致性(consistency): 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所作的修改有一部分已经写入物理数据库,这时数据库就处于一种不正确状态 或者说是 不一致状态
  • 隔离性(isolation): 一个事务的执行不能其他事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰
  • 持续性(durability): 一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的,接下来的其他操作或故障不应该对其执行结果有任何影响

什么是脏读:

是一个事务在处理过程中读取了另一个事务未提交的数据,当一个事务正在访问数据并且进行了修改,但是还没提交事务,这时另外一个事务也访问了这个数据,然后使用了这个数据,因为这个数据的修改还没提交到数据库,所以另外一个事务读取的数据就是"脏数据"

什么是丢失修改:

是指一个事务读取一个数据时,另外一个数据也访问了改数据,那么在第一个事务修改了这个数据之后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,这种情况被称为修改丢失

什么是不可重复读:

在一个事务内多次读取同一数据,在这个事务还没结束时,另外一个事务也访问了这个数据并对这个数据进行了修改,那么就可能造成第一个事务多次读取的数据不一致

什么是幻读:

是指同一个事务内多次查询返回结果集总数不一样(比如增加了或者减少了行记录)

不可重复读 和 幻读的区别:

  • 不可重复读 针对的是一份数据的修改
  • 幻读 针对的是行数修改

事务隔离级别:

事务隔离级别 脏读 不可重复读(被修改) 幻读(删减)
读未提交( read - uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

InnoDB 存储引擎默认的事务隔离级别是可重复读

如何选择事务隔离级别:

隔离级别越低,事务请求的锁越少相应性能也就越高,如果没有特殊要求或发生错误,使用默认的隔离级别即可,如果系统中有高频读写并且对一致性要求高那么就需要比较高的事务隔离级别甚至串行化

一致性非锁定读和非锁定读:

锁定读: 使用读写锁

  • 每次读操作需要获取一个共享(读)锁,每次写操作需要获取一个写锁
  • 共享锁之间不会产生互斥,共享锁和写锁之间、以及写锁与写锁之间会产生互斥
  • 当产生锁竞争时,需要等待其中一个操作释放锁后,另一个操作才能获取锁

锁机制: 解决了多个事务同时更新数据,此时必须要有一个加锁的机制

  • 行锁:解决的是多个事务同时更新一行数据
  • 间隙锁: 解决的就是多个事务同时更新多行数据

非锁定读: 使用 mvcc 多版本控制实现

MVCC 内部细节:

Multi-Version Concurrency Control 多版本并发控制, MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问

InnoDB 是一个多版本的存储引擎。它保存有关已更改行的旧版本的信息,以支持并发和回滚等事务特性。这些信息存储在一个称为回滚的数据结构中的系统表空间 或 undo 表空间中。 InnoDB 使用回滚段中的信息来执行事务回滚所需要的撤销消息。它还使用这些信息构建行的早期版本,以实现一致性的读取。

MVCC 的实现依赖于: 隐藏字段、 Read View、undo log

隐藏字段:

  • A 6-byte DB_TRX_ID 用来标识最近一次对本行记录做修改(insert 、 update) 的事务的标识符,即最后一次修改本行记录的事务 ID。如果是 delete 操作,在 InnoDB 存储引擎内部也属于一次 update 操作,即更新行中的一个特殊位,将行标识为已删除,并非真正删除。
  • A 7-byte DB_ROLL_PTR 回滚指针,指向该行的 undo log,如果改行未被更新,则为空
  • A 6-byte DB_ROW_ID 如果没有设置主键且该表没有唯一非空索引时, InnoDB 会使用该 ID 来生成聚簇索引

Read View:

  • 不同的事务隔离级别中,当有事务在执行过程中修改了数据(更新版本号), 在并发事务时需要判断以下版本链中的哪个版本是当前事务可见的。 为此 InnoDB 有了 Read View 的概念,使用 ReadView 来记录和隔离不同事务并发时此记录的哪些版本是对当前访问事务可见的

Undo log:

  • 除了用来回滚数据,还可以读取可见版本的数据。以此实现非锁定读

MySQL 事务一致性,原子性是如何实现的:

  • 首先是通过锁和mvcc 实现了执行过程中的一致性和原子性
  • 其次在灾备方面通过 Redo log 实现, Redo log 会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统崩溃重启后可以把事务所作的任何修改都恢复出来

MySQL 事务的持久性是如何实现:

使用 Redo log 保证了事务的持久性。当事务提交时,必须先将事务的所有日志写入日志文件进行持久化,就是我们常说的 WAL(write ahead log)机制,如果出现断电重启便可以从 redo log 中恢复,如果 redo log 写入失败那么也就意味着修改失败整个事务也就直接回滚了

表级锁和行级锁:

  • 表级锁: 串行化(serializable)时,整表加锁,事务访问表数据时需要申请锁,虽然可分为读锁和写锁,但毕竟是锁住了整张表,会导致并发能力下降,一般是做ddl 处理时使用
  • 行级锁:除了串行化(serializable)时 InnoDB 使用的都是行级锁,只锁一行数据,其他行数据不影响,并发能力强

共享锁(读锁)或 S锁:其他事务可以继续加共享锁,但不能加排它锁

排它锁(写锁/独占锁)或 X 锁: 在进行写操作之前要申请并获得,其他事务不能再获得任何锁

意向锁:

  • 分为意向共享锁(IS) 和 意向排它锁(IX)
  • 一个事务对一张表的某行添加共享锁前,必须获得该表一个 IS 锁或者 优先级更高的锁
  • 一个事务对一张表的某行添加排它锁之前,必须对该表或者一个 IX 锁
  • 意向锁属于表锁,它不与 InnoDB 中的行锁冲突,任意两个意向锁之间也不会产生冲突,但是会与表锁(S锁和X锁)产生冲突

当前读和快照读:

当前读:在锁定读(使用锁隔离事务) 的时候读到的最新版本的数据

快照读:  可重复读(repeatable-read) 下 MVCC 生效读取的数据的快照,并不是最新版本的数据(未提交事务的数据)

XA 协议:

MySQL 事务_第1张图片

 AP(Application Program) 应用程序:定义事务边界(定义事务开始和结束)并访问事务边界内的资源

RM(Resource Manger) 资源管理器: 管理共享资源并提供外部访问接口。共外部程序来访问数据库等共享资源。此外,RM 还具有事务的回滚能力。

TM(Transaction Manger) 事务管理器: TM 是分布式事务的协调者,TM与每个RM进行通信,负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等。

  1. 应用程序 AP 向事务管理器 TM 发起事务请求
  2. TM 调用 xa_open() 尽力同资源管理器的会话
  3. TM 调用 xa_start() 标记一个事务分支的开头
  4. AP 访问资源管理 RM 并定义操作,比如插入记录操作
  5. TM 调用 xa_end() 标记事务分支的结束
  6. TM 调用 xa_prepare() 通知 RM 做好事务分支的提交准备工作。起始就是二阶段提交的提交请求阶段
  7. TM 调用 xa_commit() 通知 RM 提交事务分支,也就是二阶段提交的提交执行阶段
  8. TM 调用 xa_close 管理 与 RM 的会话

这些接口 一定要按顺序执行,比如 xa_start 接口一定要在 xa_end 之前。此外,这里千万要注意的事务管理器只是标记事务分支并不执行事务,事务操作最终由应用程序通知资源管理完成的。

XA 的接口:

  • xa_start: 负责 开启或恢复一个事务分支,并且管理 XID到调用线程
  • xa_end: 负责取消当前线程与事务分支的关系
  • xa_prepare: 负责询问 RM 是否准备好了提交事务分支
  • xa_commit:通知 RM 提交事务分支
  • xa_rollback: 通知 RM 回滚事务分支

MySQL xa 事务:

  • InnoDB 内部本地普通事务操作协调数据写入与log 写入两阶段提交
  • 外部分布式事务
SHOW VARIABLES LIKE '%innodb_support_xa%'; --5.7
-- 8.0 默认开启无法关闭

-- xa 事务语法
XA START '自定义事务ID';

-- SQL 语句...

XA END '自定义事务ID';

XA PREPARE '自定义事务ID';  -- 执行成功后,事务信息将被持久化。即使会话中止甚至应用服务宕机,只要我们['自定义事务ID'] 记录下来,后续仍然可以使用它对事务进行 rollback 或者 commit

XA COMMIT\ROLLBACK '自定义事务ID';

select for update:

select 本身是一个查询语句,查询语句是不会产生冲突的一种行为,一般情况下没有锁的, 用 select for update 会让 select 语句产生一个排它锁(X), 这个锁 和 update 的效果一样,会使两个事务无法同时更新一条记录。

  • for update 仅适用于 InnoDB, 且必须在事务块(BEGIN/COMMIT) 中才能失效
  • 在进行事务操作时,通过 "for update"语句, MySQL 会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排它锁包含行锁、表锁
  • InnoDB 默认是行级锁,在筛选条件中有明确指定主键或唯一索引列的时候,是行级锁,否则是表级锁

解决死锁问题:

  1. 死锁无法避免,上线前要进行严格的压力测试
  2. 快速失败 innodb_lock_wait_timeout 行锁超时时间
  3. 拆分 sql, 严禁大事务
  4. 充分利用索引、优化索引、尽量把风险的事务SQL 适用上覆盖索引,优化 where 条件前缀匹配,提升查询速度,减少表锁
  5. 无法避免时:
  • 操作多张表时,尽量以相同的顺序来访问避免形成等待环路
  • 单张表先排序再操作
  • 适用排它锁 比如 for update

你可能感兴趣的:(MySQL,mysql,数据库)