引言:

当crud不被控制会发生什么场景?以我们日常买票的12306来看,当只剩一张票的时候,两个人同时买票,如果不加以控制就会出现票数为负的现象(如上图)

试想数据库操作必须有哪些特性才能解决上面问题呢

1. 买票的过程得是原子的吧

2. 买票互相应该不能影响吧

3. 买完票应该要永久有效吧

4. 买前,和买后都要是确定的状态吧

事务

什么是事务?

事务 是指在数据库中执行的一组操作,这些操作要么全部成功,要么全部失败。事务的主要目的是确保数据的一致性和完整性。简单来说事务就是一组DML语言要么同时成功要么同时失败。

事务四大特性

通过上面的例子,事务理应具备以下特性

简称为 ACID:原子性、一致性、隔离性和持久性。

原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable )

持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

为什么会出现事务?

事务被 MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题.可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服务的.而不是伴随着数据库系统天生就有的.

事务操作

1.在mysql数据库中只有Innodb引擎可以使用事务

2.在 MySQL 中,事务可以分为隐式事务和显式事务。隐式事务是指 MySQL 自动管理事务的开启和提交,而显式事务则需要开发者手动控制。

3.隐式事务在执行 INSERTUPDATEDELETE 等操作时,MySQL 会自动开启和提交事务。是否开启隐式事务由变量 autocommit 控制。

查看 autocommit 变量
SHOW VARIABLES LIKE ‘autocommit’;

4.显式事务需要手动开启、提交或回滚。可以使用以下两种方式:

SET autocommit=0; — 关闭自动提交
— 执行事务操作
COMMIT; — 提交事务
ROLLBACK; — 回滚事务

START TRANSACTION; — 开启事务
— 执行事务操作
COMMIT; — 提交事务
ROLLBACK; — 回滚事物

事务的隔离级别

1.MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行

2.一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。

3.但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。

4.就如同你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不关心。那么你的学习,对你妈妈来讲,就是原子的。那么你学习过程中,很容易受别人干扰,此时,就需要将你的学习隔离开,保证你的学习环境是健康的。

5.数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性

6.数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别

隔离级别读未提交【Read Uncommitted】: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。

读提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。

可重复读【Repeatable Read】: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。

串行化【Serializable】: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)

隔离级别操作:

查看当前事务的隔离级别
SELECT @@tx_isolation;
设置事务的隔离级别
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL ‘隔离级别名称’;

隔离级别脏读不可重复读幻读性能
读未提交最高
读已提交
可重复读
串行化最低

MVCC

在可重复读【Repeatable Read】中还是会引起幻读问题,于是mysql就引入了MVCC,多版本并发控制是一种用来解决 读-写冲突 的无锁并发控制

1.为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照。

2.所以 MVCC 可以为数据库解决以下问题在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

 MVCC 的工作原理:

1. 隐藏字段

每行记录都有三个隐藏字段:

DB_TRX_ID:最后一次修改该行的事务 ID。

DB_ROLL_PTR:回滚指针,指向 undo log 中的旧版本。

DB_ROW_ID:隐含的自增 ID(无主键时用于聚簇索引)。

2. Undo Log(版本链)

每次更新数据时,旧版本被写入 undo log,形成一条版本链

最新版本 ←→ 旧版本 ←→ 更旧版本 ←→ ...

3. Read View(一致性视图)

每个事务在启动时(或第一次读时)会生成一个 Read View,包含:

当前活跃事务 ID 列表。

最小活跃事务 ID(min_trx_id)。

最大事务 ID(max_trx_id)。

创建该 Read View 的事务 ID(creator_trx_id)。

可读性规则

class ReadView {
// 省略...
private:
/** 高水位,大于等于这个ID的事务均不可见*/
trx_id_t m_low_limit_id我们在实际读取数据版本链的时候,是能读取到每一个版本对应的事务ID的,即:当前记录的DB_TRX_ID 。
那么,我们现在手里面有的东西就有,当前快照读的 ReadView 和 版本链中的某一个记录的
DB_TRX_ID 。
所以现在的问题就是,当前快照读,应不应该读到当前版本记录。一张图,解决所有问题!
/** 低水位:小于这个ID的事务均可见 */
trx_id_t m_up_limit_id;
/** 创建该 Read View 的事务ID*/
trx_id_t m_creator_trx_id;
/** 创建视图时的活跃事务id列表*/
ids_t m_ids;
/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
trx_id_t m_low_limit_no;
/** 标记视图是否被关闭*/
bool m_closed;
// 省略...
};
条件是否可见
版本 trx_id < min_trx_id✅ 已提交
版本 trx_id > max_trx_id❌ 未来事务
版本 trx_id 在活跃列表中❌ 未提交
版本 trx_id == creator_trx_id✅ 自己改的

举例说明:

当select时就会保存快照,根据上面的规则去查询已提交的事务和未提交的事务。

MVCC 的优点与限制

优点限制
读不阻塞写,写不阻塞读需要额外存储 undo log
高并发性能长时间事务可能导致“版本链”过长,影响性能
支持一致性非锁定读不支持解决所有幻读(InnoDB 通过间隙锁解决)

总结MVCC 是数据库实现“高效并发控制”的核心技术,通过保存数据的历史版本,让读写互不阻塞,从而提升性能并保证一致性。

今天的更新就到这里,如有错误欢迎评论区指出!!!