开始时间:2022-10-03
课程链接:MySQL数据库教程
JavaGuide
小林coding
数据库锁
共享锁和独占锁
我们来测试一下
//对读取的记录加共享锁
select ... lock in share mode;
//对读取的记录加独占锁
select ... for update;
mission1
BEGIN;
SELECT * FROM dept lock in share MODE;
mission2
BEGIN;
SELECT * FROM dept lock in share MODE;
此时都能查出结果
因为没有commit,所以其实都没释放锁
mission1
独占【排它锁】形式
BEGIN;
SELECT * FROM dept for UPDATE;
能够查到
但我没commit
此时mission2
BEGIN;
SELECT * FROM dept lock in share MODE;
读不到数据,卡死了
mission1提交commit,此时mission2才能读到数据
如果mission2超过某个阈值还没获取到,报错
查询数据库哪些表加了锁
上一个锁后我们再看
lock tables dept read
show open tables where in_use>0;
释放表锁
UNLOCK TABLES
也可以加写锁
lock tables dept write;
测试读锁条件下写
lock tables dept READ;
update dept set DEPTNO='50' where loc='NEW YORK';
UNLOCK TABLES
不行
写锁条件下写
lock tables dept WRITE;
update dept set DEPTNO='60' where loc='NEW YORK';
UNLOCK TABLES
没问题
写锁条件下访问其他表
lock tables dept WRITE;
update emp set ENAME='SMITH_2' where ENAME='SMITH';
UNLOCK TABLES
不行,不给访问
update emp set ENAME='SMITH_2' where ENAME='SMITH'
> 1100 - Table 'emp' was not locked with LOCK TABLES
> 时间: 0s
意向锁
在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个==「意向共享锁」;
在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」==;
在数据表的场景中,如果我们给某一行数据加上了排它锁,数据库会自动给更大一级的空间,比如数据页或数据表加上意向锁,告诉其他人这个数据页或数据表已经有人上过排它锁了,这样当其他人想要获取数据表排它锁的时候只需要了解是否有人已经获取了这个数据表的意向排他锁即可。就不用一行一行去判断是否有加锁了
意向锁不和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(lock tables … read)和独占表锁(lock tables … write)发生冲突
MySQL死锁
当我们两个事务里都有间隙锁
那么两个事务都在这个间隙里面插入数据,就会造成死锁
Deadlock found when trying to get lock; try restarting transaction
事务一
BEGIN;
SELECT * from dept where DEPTNO=21 for UPDATE;
事务二
BEGIN;
SELECT * FROM dept where DEPTNO=24 lock in share MODE;
此时事务一执行
INSERT into dept(DEPTNO,DNAME,LOC) values(21,'haha','beijing');
事务二执行
INSERT into dept(DEPTNO,DNAME,LOC) values(23,'haha2','beijing');
报死锁的错误
都是有间隙锁,锁的同一范围
我们插入的时候都等对方间隙锁的释放
锁监控
日志
binlog
只记录更新操作,不记录查询操作,二进制形式存储。
如果还记录查询【那和通用查询日志没区别了。。】
- 一是用于数据恢复,如果MySQL数据库意外停止,可以通过二进制日志文件来查看用户执行了哪些操作,对数据库服务器文件傲了哪些修改,然后根据二进制日志文件中的记录来恢复数据库服务器。
- 二是用于数据复制,由于日志的延续性和时效性,master把它的二进制日志传递给slaves来达到master-slave数据一致的目的。
查一下binlog放哪的
show VARIABLES like '%log_bin%';
我看没有开启
实现主从复制就要依靠binlog
主库将数据库中数据的变化写入到 binlog
从库连接主库
从库会创建一个 I/O 线程向主库请求更新的 binlog
主库会创建一个 binlog dump 线程来发送 binlog ,从库中的 I/O 线程负责接收
从库的 I/O 线程将接收的 binlog 写入到 relay log 中。
从库的 SQL 线程读取 relay log 同步数据本地(也就是再执行一遍 SQL )
binlog在数据库层【server层】产生,redolog在存储引擎层产生。
数据复制问题
-
异步复制
异步模式就是客户端提交COMMIT之后不需要等从库返回任何结果,而是直接将结果返回给客户端。
这样做的好处是不会影响主库写的效率,但可能会存在主库宕机,而Binlog还没有同步到从库的情况,也就是此时的主库和从库数据不一致。这时候从从库中选择一个作为新主,那么新主则可能缺少原来主服务器中已提交的事务。所以,这种复制模式下的数据一致性是最弱的。 -
半同步复制
在客户端提交COMMIT之后不直接将结果返回给客户端,而是等待至少有一个从库接收到了Binlog,并且写入到中继日志中,再返回给客户端。
这样做的好处就是提高了数据的一致性,当然相比于异步复制来说,至少多增加了一个网络连接的延迟,降低了主库写的效率。 -
组复制
首先我们将多个节点共同组成一个复制组,在执行读写(RW))事务的时候,需要通过一致性协议层(Consensus层)的同意,也就是读写事务想要进行提交,必须要经过组里""大多数人”(对应Node节点)的同意,大多数指的是同意的节点数量需要大于(N/2+1),这样才可以进行提交,而不是原发起方一个说了算。而针对只读(RO)事务则不需要经过组内同意,直接COMMIT即可。
在一个复制组内有多个节点组成,它们各自维护了自己的数据副本,并且在一致性协议层实现了原子消息和全局有序消息,从而保证组内数据的一致性。
undolog
在事务执行中途发生了 MySQL 崩溃后,就不用担心无法回滚到事务之前的数据,我们可以通过这个日志回滚到事务之前的数据。
UNDO LOG称为回滚日志,回滚行记录到某个特定版本。在事务没提交之前,MySQL 会先记录更新前的数据到 undo log 日志文件里面,当事务回滚时,可以利用 undo log 来进行回滚。
用来保证事务的原子性的
undo log是存储引擎层(innodb)生成的日志,记录的是逻辑操作日志,比如对某一行数据进行了INSERT语句操作,那么undo log就记录一条与之相反的DELETE操作。主要用于事务的回滚(undo log记录的是每个修改操作的逆操作)和一致性非锁定读(undo log回滚行记录到某种特定的版本—MVCC,即多版本并发控制)。
redolog
InnoDB存储引擎是以页为单位来管理存储空间的。
在真正访问页面之前,需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问【我们并不是直接操作磁盘哟】。所有的变更都必须先更新缓冲池中的数据,然后缓冲池中的脏页会以一定的频率被刷入磁盘( checkPoint机制),通过缓冲池来优化CPu和磁盘之间的鸿沟,这样就可以保证整体的性能不会下降太快。
但是刷盘也有讲究,简单粗暴的每次修改都刷盘,有可能我们只修改了一个字节,但却要把整页16字节的都刷盘进去,太浪费了。
另外一个问题就是我们修改的页面很可能不相邻,那每次Buffer Pool刷盘,需要做很多随机IO,这是比顺序IO慢的操作。
redo的作用
之前是内存->磁盘 比较慢
现在我们是内存>redo日志->磁盘
先写日志,再写磁盘
redo是实际的物理日志。记录了某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新,每当执行一个事务就会产生这样的一条或者多条物理日志。
- redo log 记录了此次事务「完成后」的数据状态,记录的是更新之后的值;
- undo log 记录了此次事务「开始前」的数据状态,记录的是更新之前的值;
事务提交之前发生了崩溃,重启后会通过 undo log 回滚事务,事务提交之后发生了崩溃,重启后会通过 redo log 恢复事务
因为写入 redo log 的方式使用了追加操作, 所以磁盘操作是顺序写,这可比随机IO快
但redo本身是磁盘写,那还是慢,=》所以redo也整了一个 redo buffer【万事万物都搞buffer。。】
redolog buffer->redolog file【追加写】
- binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志。redo log 是循环写,日志空间大小是固定,全部写满就从头开始,保存未被刷入磁盘的脏页日志。
- binlog 用于备份恢复、主从复制;redo log 用于掉电等故障恢复。
为什么我们不用redo log恢复日志
因为 redo log 文件是循环写,是会边写边擦除日志的,只记录未被刷入磁盘的数据的物理日志,已经刷入磁盘的数据都会从 redo log 文件里擦除。
binlog和redolog的两阶段提交
在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的写入时机不一样。
- 如果在将 redo log 刷入到磁盘之后, MySQL 突然宕机了,而 binlog 还没有来得及写入
- 如果在将 binlog 刷入到磁盘之后, MySQL 突然宕机了,而 redo log 还没有来得及写入
事务的提交过程有两个阶段,就是将 redo log 的写入拆成了两个步骤:prepare 和 commit,中间再穿插写入binlog
两阶段提交是以 binlog 写成功为事务提交成功的标识,因为 binlog 写成功了,就意味着能在 binlog 中查找到与 redo log 相同的 XID。如果有就提交事务,如果没有就回滚事务。
两阶段的不足
磁盘 I/O 次数高
锁竞争激烈【为了保证有序】
结束时间:2022-10-06