目录
1.我们为什么需要缓存策略,MySQL的缓存方案是用来做什么的?
三.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数据不一致