讲讲MySQL数据库事务怎么实现的!
什么是数据库事务
数据库事务是指一组数据库操作,这些操作必须被视为一个不可分割的单元,要么全部执行成功,要么全部失败回滚。事务通常由多个SQL语句组成,这些语句可以读取、插入、更新或删除数据库中的数据。事务具有ACID属性:
1. 原子性(Atomicity):事务的所有操作被视为单个原子操作,要么全部执行成功,要么全部执行失败回滚。
2. 一致性(Consistency):事务执行的结果必须使数据库从一个一致性状态转换到另一个一致性状态,其中包括所有数据完整性和约束性规则的应用。
3. 隔离性(Isolation):一个事务的执行不能被其他并发执行的事务干扰,每个事务应该感觉自己在独立地执行。
4. 持久性(Durability):一旦事务提交,其结果应该持久保存在数据库中,即使系统故障也应该如此。
通过实现事务,数据库系统可以确保数据的完整性和一致性,以及并发访问时的正确性。如果一个事务中的任何一个操作失败,整个事务将被回滚到最初的状态,这确保了数据库的一致性。
Mysql如何保证原子性
undo log名为回滚日志,是实现原子性的关键。InnoDB把这些为了回滚而记录的这些东西称之为undo log。这里需要注意的一点是,由于查询操作(SELECT)并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的undo log。undo log主要分为3种:
• Insert undo log :插入一条记录时,至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉就好了。
• Update undo log:修改一条记录时,至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值就好了。
• Delete undo log:删除一条记录时,至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了。
• 删除操作都只是设置一下老记录的DELETED_BIT,并不真正将过时的记录删除。
• 为了节省磁盘空间,InnoDB有专门的purge线程来清理DELETED_BIT为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的DELETED_BIT为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。
举个栗子:
sql | undo log |
insert | delete |
delete | insert |
update T set v=3 where v=1 | update T set v=1 where v=3 |
Mysql如何保证持久性
通过Innodb架构解析我们了解到InnoDB 为了提升读写效率,引入了Buffer Pool(缓存池):
- • 当数据库读取数据时,会首先从缓存池中读取
- • 往数据库写入数据时,会先写入缓存池
- • 缓存池中更新的数据会定期刷新到磁盘中
如果MySQL宕机,缓存池中更新的数据还没有刷回到磁盘中,就会导致数据丢失。于是,redo log被引入进来解决这个问题。
图片
1. 先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝。
2. 生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值。
3. 当事务commit时,将redo log buffer中的内容刷新到 redolog file,对 redo log file采用追加写的方式。
4. 定期将内存中修改的数据刷新到磁盘中。
redo与undo在一次事务操作中是如何交互的?假设有A、B两个数据,值分别为1、2,开启事务分别对其进行修改A → 3,B → 4,在提交,过程如下:
事务 | redo&undo logo |
begin; | 开启事务 |
记录A->3到redo log buffer | |
update T set A=3 where A=1; | A修改为3 |
记录A=1到undo log | |
记录B->4到redo log buffer | |
update T set B=4 where B=2; | B修改为4 |
记录B=2到undo log | |
记录A->3到redo log记录B->4到redo log | |
commit; | 事务提交 |
MySQL怎么保证隔离性
事务在并发情形下会互相干扰到的操作大体可以分为两类,与之相对应地,MySQL采用了两种方式来实现它们的隔离:
1. 一个事务的写操作对另一个事务的写操作的影响:锁机制保证隔离性
2. 一个事务的写操作对另一个事务的读操作的影响:MVCC保证隔离性
加锁:读取数据之前,对其加锁,阻止其他事务对数据进行修改
MVCC:不加任何锁,采用多版本并发控制实现,把数据库的行锁和行的多个版本结合起来,可以实现非锁定读,从而提高数据库的并发性能。
事务隔离级别
当数据库上有多个事务同时执行的时候,会带来以下问题:
问题 | 描述 | 举例 |
脏读 | 一个事务读到了另一个事务未提交修改的数据。 | 事务A开始一个更新操作,但是还没有提交,这时事务B读取了这个未提交的数据,就会产生脏读。 |
幻读 | 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据。 | 事务A进行一个范围查询,此时事务B插入了一些符合该范围查询条件的新数据,当事务A再次进行相同的范围查询时,会发现多了一些之前没有的行,就产生了幻读。 |
不可重复读 | 在一个事务中,多次查询的数据不一致。 | 事务A读取了一行数据,然后事务B对这一行数据进行了更新,并且提交了,当事务A再次读取这一行数据时,会发现数据已经发生了变化,就产生了不可重复读。 |
为了避免这些问题的出现,数据库引入了隔离级别的概念,通过对不同隔离级别的设置,可以控制事务之间的隔离程度,从而避免并发问题的产生。不同的隔离级别有不同的特点和使用场景,需要根据实际情况进行选择。
以下是四个标准的事务隔离级别:
隔离级别 | 含义 | 脏读 | 不可重复读 | 幻读 |
读未提交,Read Uncommitted | 事务中的修改,即使没有提交,对其他事务都是可见的 | Y | Y | Y |
读已提交,Read Committed | 事务从开始到提交之前,所做的修改对其他事务都不可见 | N | Y | Y |
可重复读,Repeatable read | 同一事务中多次读取同样的记录结果是一致的 | N | N | Y |
可序列化,Serializable | 在读取的每一行数据上加锁,强制事务串行执行 | N | N | N |
脏读的解决
Innodb是通过在每行数据中增加一个隐藏的事务ID来实现mvcc,当一个事物开始时他会获取一个唯一的事务ID,该事务ID用来标记事务做的修改。当事务读取一行数据时,innodb会检查该行数据事务ID是否小于当前事务ID,如果是说明该行数据是未提交的数据,innodb会阻止该事务读取该行数据,从而避免了脏读的问题。
不可重复读的解决
innodb通过mvcc解决不可重复读的问题,在RR数据库隔离级别下,当我们使用快照进行数据读取的时候,只会在第一次读取的时候生成一个ReadView,后续所有快照读都是使用同一个快照,所以就不会发生不可重复读的问题了。
可重复读模式下举个栗子:事务隔离级别为RR:
图片
创建个测试表,并插入一条数据(1,1,1)
create table table1(
id int(11) not null,
a varchar(50) default null,
b varchar(50) default null,
primary key(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
分别开启两个事务测试:
事务1 | 事务2 |
启动事务,查询如下: | 启动事务,查询如下: |
~ | 将a改为2,可以查到 |
查询a的结果还是1 | |
~ | 提交事务 |
再次查询a的结果还是1 | |
提交事务,再次查询a的结果变为2了 |
幻读的解决
innodb的mvcc和间隙锁在一定程度上避免了幻读的发生,但是没有办法完全避免,当一个事务读的时候会导致幻读的发生。
幻读的case:
- • 创建一个用户表
create table user(
id int not null,
name varchar(50),
age int,
primary key(id)
);
- • 插入几条数据
insert into user values(1,'张三',10),(2,'李四',20),(3,'王二',30);
- • 分别开启两个事务测试:
事务1 | 事务2 |
begin;select * from user where age >10 and age10 and age10 and age 回到顶部 |