目录
三.MySQL事务原理分析
1.事务是什么?
2.执行事务的目的是什么?
3.事务是由什么组成的?
4.事务的特征是什么?
5.事务控制语句
6.ACID特性
6.1原子性(A)
6.2隔离性(I)
6.3持久性(D)
6.4 一致性(C)
7.事务的隔离级别
7.1READ UNCOMMITTED (读为提交)
7.2READ COMMITTED (读已提交)
7.3REPEATABLE READ (可重复读)
7.4设置隔离级别的命令
7.5 不同隔离级别的并发异常
8.锁
8.1全局锁
8.2表级锁
8.3行级锁
8.4 查询语句中怎么添加锁
8.5 删除,更新操作
8.6 插入操作
9.死锁
9.1并发死锁
9.2相反加锁顺序死锁
9.3锁冲突死锁
9.4解决死锁
9.5如何避免死锁
四.MySQL缓存策略
1.我们为什么需要缓存策略,MySQL的缓存方案是用来做什么的?
2.在什么场景我们需要考虑使用缓存策略呢?
3.缓存方案是怎么解决问题的?
3.1通常情况下 数据的存储通常有以下几种状态
3.2我们通常有读写策略去解决缓存一致性问题
3.3同步方案
4.缓存方案的故障问题及解决
4.1缓存穿透
4.2缓存击穿
4.3缓存雪崩
5.缓存方案的弊端
三.MySQL事务原理分析
1.事务
1.事务是什么?
事务的本质是并发控制的单元,是用户定义的一个操作序列。 这些操作要么都做,要么都不做,是一个不可分割的工作单位。
2.执行事务的目的是什么?
事务将数据库从一种一致性状态转换为另一种一致性状态;保 证系始终处于一个完整且正确的状态;
3.事务是由什么组成的?
事务可由一条非常简单的 SQL 语句组成,也可以由一组复杂的 SQL 语句组成;
4.事务的特征是什么?
在数据库提交事务时,可以确保要么所有修改都已经保存,要 么所有修改都不保存;事务是访问并更新数据库各种数据项的一个程序执行单元。 在 MySQL innodb 下,单条语句都具备事务;可以通过 set autocommit = 0; 设置当前会话手动提交;
5.事务控制语句
-- 显示开启事务
START TRANSACTION | BEGIN
-- 提交事务,并使得已对数据库做的所有修改持久化
COMMIT
-- 回滚事务,结束用户的事务,并撤销正在进行的所有未提交的
修改
ROLLBACK
-- 创建一个保存点,一个事务可以有多个保存点
SAVEPOINT identifier
-- 删除一个保存点
RELEASE SAVEPOINT identifier
-- 事务回滚到保存点
ROLLBACK TO [SAVEPOINT] identifier
6.ACID特性
6.1原子性(A)
事务操作要么都做(提交),要么都不做(回滚);事务是访 问并更新数据库各种数据项的一个程序执行单元,是不可分割 的工作单位;通过 undolog 来实现回滚操作。 undolog 记录的 是事务每步具体操作,当回滚时,回放事务具体操作的逆运算;事务包含的全部操作是一个不可分割的整体,要么全部执行, 要么全部不执行;
6.2隔离性(I)
描述:各个事务之间互相影响的程度;
目的:主要规定多个事务访问同一数据资源,各个事务对该数 据资源访问的行为,不同的隔离性是应对不同的现象(脏读、 不可重复读、幻读)
事务的隔离性要求每个读写事务的对象对其他事务的操作对象 能相互分离,并发事务之间不会相互影响,设定了不同程度的 隔离级别,通过适度破环一致性,得以提高性能 ;通过 MVCC 和 锁来实现;MVCC 时多版本并发控制,主要解决一致性非锁 定读 ,通过记录和获取行版本,而不是使用锁来限制读操作, 从而实现高效并发读性能。锁用来处理并发 DML 操作;数据库 中提供粒度锁的策略,针对表(聚集索引 B+ 树)、页(聚集索 引 B+ 树叶子节点)、行(叶子节点当中某一段记录行)三种粒度加锁;
6.3持久性(D)
事务一旦完成,要将数据所做的变更记录下来,包括数据存储 和多副本的网络备份;事务提交后,事务 DML 操作将会持久化(写入 redolog 磁盘文件 哪一个页 页偏移值 具体数据);即使发生宕机等故障,数据 库也能将数据恢复。redolog 记录的是物理日志;
6.4 一致性(C)
事务的前后,所有的数据都保持一个一致的状态,不能违反数 据的一致性检测(完整性约束检查);一致性指事务将数据库从一种一致性状态转变为下一种一致性 的状态,在事务执行前后,数据库完整性约束没有被破坏;一个事务单元需要提交之后才会被其他事务可见。例如:一个表 的姓名是唯一键,如果一个事务对姓名进行修改,但是在事务 提交或事务回滚后,表中的姓名变得不唯一了,这样就破坏了一致性;一致性由原子性、隔离性以及持久性共同来维护的。
7.事务的隔离级别
ISO 和 ANIS SQL 标准制定了四种事务隔离级别的标准,各数据 库厂商在正确性和性能之间做了妥协,并没有严格遵循这些标准;MySQL innodb 默认支持的隔离级别 是 REPEATABLE READ
目的:提升mysql并发处理SQL语句的性能
7.1READ UNCOMMITTED (读为提交)
读未提交;该级别下读不加锁,写加排他锁,写锁在事务提交 或回滚后释放锁;
读:不做任何处理
写:自动加X锁(写锁)
7.2READ COMMITTED (读已提交)
读已提交( RC );从该级别后支持 MVCC ( 多版本并发控制 ) ,也 就是提供一致性非锁定读;此时读取操作读取历史快照数据; 该隔离级别下读取历史版本的最新数据,所以读取的是已提交的数据;
读:mvcc,读取最新版本的行数据
写:自动加X锁(写锁)
7.3REPEATABLE READ (可重复读)
可重复读( RR );该级别下也支持 MVCC ,此时读取操作读取 事务开始时的版本数据;
读:mvcc,读取事务开始前版本的行数据
写:自动加X锁(写锁)
7.4 SERIALIZABLE (可串行化)
可串行化;该级别下给读加了共享锁;所以事务都是串行化的 执行;此时隔离级别最严苛;
读:自动加S锁
写:自动加X锁
最安全的也是效率最低的
7.4设置隔离级别的命令
-- 设置隔离级别
SET [GLOBAL | SESSION] TRANSACTION ISOLATION
LEVEL REPEATABLE READ;
-- 或者采用下面的方式设置隔离级别
SET @@tx_isolation = 'REPEATABLE READ';
SET @@global.tx_isolation = 'REPEATABLE READ';
-- 查看全局隔离级别
SELECT @@global.tx_isolation;
-- 查看当前会话隔离级别
SELECT @@session.tx_isolation;
SELECT @@tx_isolation;
-- 手动给读加 S 锁
SELECT ... LOCK IN SHARE MODE;
-- 手动给读加 X 锁
SELECT ... FOR UPDATE;
-- 查看当前锁信息
SELECT * FROM information_schema.innodb_locks;
7.5 不同隔离级别的并发异常
脏读:一个事务读到另一个未提交事务修改的数据不可重复读:一个事务内两次读取的数据不一样幻读:一个事务内两次读取同一个范围内的记录得到的结果集不一样
8.锁
锁机制用于管理对共享资源的并发访问;用来实现事务的隔离 级别 ;
8.1全局锁
flush tables with read lock
:属于全局锁指令,执行后会让数据库所有表关闭并加读锁,使整个数据库进入只读状态,其他会话只能读数据,不能执行写操作。常用于全库备份场景,确保备份期间数据状态稳定、一致。
unlock tables
:用于释放由flush tables with read lock
添加的表锁。当全库备份等操作完成后,执行该指令解除数据库锁定,让数据库恢复正常读写。
全局锁在全库备份时保障数据一致性,但会阻塞写操作,可能影响业务,使用时需谨慎安排操作时间
8.2表级锁
表锁
lock tables 'table' [read/write]
:用于手动对表加锁。加read
锁(读锁)时,其他会话可对该表并发读,但不能写;加write
锁(写锁)时,其他会话既不能读也不能写。
比如执行
lock tables users read;
,可使users
表处于只读状态供当前会话读取。
unlock tables
:用于释放通过lock tables
语句添加的表锁,让表恢复正常读写状态。
元数据锁
crud
:在进行增(Create)、删(Delete)、改(Update)、查(Read )操作时,会获取元数据锁,保证操作期间表结构等元数据不被修改,防止因结构变动影响操作结果。
alter
:执行ALTER TABLE
语句修改表结构(如添加列、修改列类型等)时,会持有元数据锁,锁定期间阻止其他对表结构或数据有冲突的操作,确保表结构修改顺利进行。
意向锁
作用是快速判断表中是否有记录已加锁。当事务要对表中某条记录加行锁时,会先在表级别加意向锁(意向读锁或意向写锁 ),表明后续要对表中记录加锁。比如事务准备对表中部分行加写锁,会先加意向写锁,若此时有其他事务想对整个表加写锁,通过检查意向锁就知道表中已有记录被锁定,避免冲突。
auto - inc 锁
是一种特殊表级锁,用于实现自增长列约束。在插入数据到有自增长列的表时会获取该锁。与一般表锁不同,它在语句结束后就释放,而非等事务结束,这样能提高插入性能,减少锁等待时间。例如向有
id
自增长列的orders
表插入数据时,插入语句执行期间获取auto - inc
锁,插入完成即释放 。
8.3行级锁
记录锁(record lock)
S 锁(共享锁):也叫读锁,多个事务可同时对同一记录加 S 锁,用于并发读场景,加锁事务能读取记录。例如多个事务同时读取商品表中某商品记录价格,都可加 S 锁,互不干扰。
X 锁(排他锁):即写锁,一个记录被加 X 锁后,其他事务不能再对其加任何锁,加 X 锁事务可读写记录,保证写操作原子性,防止其他事务干扰。如事务要修改商品价格,需先对该记录加 X 锁。
间隙锁(gap lock)
应用场景:在可重复读(RR)和读提交(RC)隔离级别下生效。作用是锁定记录间的间隙,防止其他事务在间隙插入新记录,避免幻读现象。比如查询年龄在 20 - 30 岁之间的用户,间隙锁会锁定 20 - 30 这个年龄区间的间隙,阻止其他事务插入此区间年龄的用户记录。
临键锁(next - key lock)
应用场景:只在可重复读(RR)隔离级别生效。它是记录锁和间隙锁的组合,锁定一个记录及其前的间隙。比如在有索引的用户表中,某条记录索引值为 5,临键锁会锁定小于等于 5 的范围,既保护该记录,也防止其他事务在其前面间隙插入数据,增强数据一致性和隔离性。
8.4 查询语句中怎么添加锁
MVCC:借助 undo log 记录历史版本,读写不互阻,提升并发性能。
S 锁(共享锁):用
lock in share mode
,允许多事务同时读,阻止其他事务写。
X 锁(排他锁):用
for update
,阻止其他事务读写。
不处理:
read uncommitted
策略,查询不加锁,易有数据问题,并发高。
查询加锁:MySQL 里,加 S 锁用
SELECT ... LOCK IN SHARE MODE
;加 X 锁用SELECT ... FOR UPDATE
。
8.5 删除,更新操作
自动添加X锁(写锁)
8.6 插入操作
insert intention lock(插入意图锁 )
特殊 gap 锁,允许多事务并行插入(位置不冲突时),同时用 X 锁防数据被改。
auto - inc lock(自动增长锁 )
特殊表锁,保证自增长列值唯一、有序 。
9.死锁
9.1并发死锁
死锁 :两个或两个以上的事务在执行过程中,因争夺锁资源而 造成的一种互相等待的现象;MySQL 中采用 wait-for graph 等待图- 采用非递归深度优先的图算法实现的方式来进行 死锁检测 ;异常报错: deadlock found when trying to get lock ;
9.2相反加锁顺序死锁
不同表的加锁顺序相反或者相同表不同行加锁顺序相反造成死锁;其中相同表不同行加锁顺序相反造成死锁有很多变种,其 中容易忽略的是给辅助索引行加锁的时候,同时会给聚集索引 行加锁;同时还可能出现在外键索引时,给父表加锁,同时隐 含给子表加锁;触发器同样如此,这些都需要视情况分析;调整加锁顺序;
9.3锁冲突死锁
innodb 在 RR 隔离级别下,最常见的是插入意向锁与 gap 锁冲 突造成死锁;主要原理为:一个事务想要获取插入意向锁,如 果有其他事务已经加了 gap lock 或 Next-key lock 则会阻塞;
9.4解决死锁
对于顺序相反型,调整执行顺序;对于锁冲突型,更换语句或者降低隔离级别;
9.5如何避免死锁
尽可能以相同顺序来访问索引记录和表;
如果能确定幻读和不可重复读对应用影响不大,考虑将隔离级别 降低为 RC ;
添加合理的索引,不走索引将会为每一行记录加锁,死锁概率非常大;
尽量在一个事务中只锁定所需要的资源,减小死锁概率;
避免大事务,将大事务分拆成多个小事务;大事务占用资源多, 耗时长,冲突概率变高;
避免同一时间点运行多个对同一表进行读写的概率;
四.MySQL缓存策略
我们以MySQL作为主数据库,Rdeis作为缓存数据库
1.我们为什么需要缓存策略,MySQL的缓存方案是用来做什么的?
我们为了降低数据库的读写压力,缓存用户定义的热点数据,用户可以直接从缓存中获取热点数据。方便用户的同时,也能降低数据库的压力。
2.在什么场景我们需要考虑使用缓存策略呢?
①内存访问速度是磁盘访问速度的十万倍以上(数量级),设置缓存策略我们可以直接从内存中获取数据,速度快,性能高,可以更快的满足用户的需求
②当读的需求远大于写的需求的同时
③mysql作为主数据库,便于统计和分析数据
④存放缓存数据库作为辅助数据库,用户存放热点数据
mysql自身缓冲层和业务无关
3.缓存方案是怎么解决问题的?
原理图
我们需要理解缓存和MySQL一致性状态分析
3.1通常情况下 数据的存储通常有以下几种状态
①MySQL有,Redis无
②MySQL无,Redis有
③都有,数据不一致
④都有,数据一致
⑤都没有
其中标红的部分为合法的,没有标红的部分为不合法的,我们在进行缓存处理的时候我们需要满足标红的三点
3.2我们通常有读写策略去解决缓存一致性问题
设置客户端进行读的时候先读缓存,缓存中如果存在则直接从缓存中返回数据给用户,缓存中如果不存在则去访问MySQL中获取,再写到Redis
客户端进行写的操作的时候通常有以下两个方案
以安全为主
先删除Redis中的数据,然后再写到MySQL中,最后把MySQL中的数据同步到Redis中,这么做可以解决MySQL无,Redis有的问题,也可以解决都有,数据不一致的问题
问题:这种方案的效率比较低,我们利用缓存主要是为了提升效率
以效率为主
先在Redis写数据并设置过期时间比如设置200ms,我们再写MySQL,最后等待MySQL把数据同步到Redis中。过期时间选择的策略是:MySQL网络传输时间+MySQL处理时间+MySQL同步到Redis的时间 通常为200-300ms之间
问题:这种方案会有一个漏洞就是如果MySQL写入失败的话 Redis200ms内的数据就是假的数据了, 就造成了200ms内MySQL无,Redis有的问题
3.3同步方案
同步方案 1
原理图
同步方案2
原理图
4.缓存方案的故障问题及解决
4.1缓存穿透
问题:数据在缓存和MySQL中都没有,客户端一直读取不存在的数据,造成MySQL服务器性能急剧下降
解决方案:
①缓存设置<key,nil>
②部署布隆过滤器 缺点:只能增加不能删除数据
4.2缓存击穿
问题:缓存中没有,MySQL中有,大量并发请求造成MySQL性能急剧下降
解决方案:
①在缓存中设置过热的数据不过期
②使用分布式锁
4.3缓存雪崩
问题:大量缓存数据失效,但是数据在MySQL中存在,造成MySQL性能急剧下降
解决方案:
①将缓存数据的过期时间分散开,避免大量数据同时过期。
②重启的时候先导入热数据到缓存(预热操作)
5.缓存方案的弊端
不能处理多语句的事务
redis不支持回滚
造成redis,MySQL数据不一致