本课程,适合具备一定Linux运维或者开发基础的朋友,课程定级中、高级DBA。
只要掌握80%,轻松助力薪资15k-25K。
课程内容均来自与MySQL官网+MySQL源码。
配套精品视频(2021 5月全新录制,版权所有:郭加磊 oldguo。),获取方法私聊。
1. 介绍
存储引擎MySQL中的“文件系统”
2. 种类
2.1 Oracle MySQL自带的存储引擎种类
mysql> show engines;
InnoDB ******
MyISAM
MRG_MYISAM
CSV
BLACKHOLE
PERFORMANCE_SCHEMA
ARCHIVE
MEMORY
FEDERATED
彩蛋: 请你列举MySQL中支持的存储引擎种类?
InnoDB、MyISAM、CSV、MEMORY
2.2 分支产品的引擎种类介绍
Percona、Mariadb
TokuDB、MyRocks、Rocksdb
特点:
1. 压缩比15倍以上
2. 插入数据性能
适应场景:
例如Zabbix监控类的平台、归档库、历史数据存储业务
3. InnoDB存储引擎特性
MVCC : 多版本并发控制
聚簇索引 : 用来组织存储数据和优化查询,IOT。
支持事务 : 数据安全保证
支持行级锁 : 控制并发
外键
多缓冲区支持
自适应Hash索引: AHI
复制中支持高级特性。
备份恢复: 支持热备。
自动故障恢复:CR Crash Recovery
双写机制:DWB Double Write Buffer
彩蛋: InnoDB 核心特性有哪些? InnoDB和MyISAM区别有哪些?
回答:
InnoDB 支持: 事务、MVCC、聚簇索引、外键、缓冲区、AHI、CR、DWB,MyISAM不支持。
InnoDB 支持: 行级锁,MyISAM支持表级锁。
InnoDB 支持热备(业务正常运行,影响低),MyISAM支持温备份(锁表备份)。
InnoDB 支持CR(自动故障恢复),宕机自动故障恢复,数据安全和一致性可以得到保证。MyISAM不支持,宕机可能丢失当前修改。
案例1 :
某期学员负责: 运维 + MySQL 工作
1.1 环境: zabbix 3.2 + centos 7.3 + mariaDB 5.5 InnoDB引擎 ,zabbix系统 监控了2000多个节点服务
1.2 现象 : 每隔一段时间zabbix卡的要死 , 每隔3-4个月,都要重新搭建一遍zabbix,存储空间经常爆满.
1.3 问题 :
1.3.1. zabbix 版本过低。
1.3.2. 数据库版本
1.3.3 zabbix数据库500G,存在一个文件里ibdata1,手工删除1个月之前的数据,空间不释放。
1.4 优化建议:
1.4.1.数据库版本升级到percona 5.7+ 版本 mariadb 10.x+,zabbix升级更高版本
1.4.2.存储引擎改为tokudb
1.4.3.监控数据按月份进行切割(二次开发:zabbix 数据保留机制功能重写,数据库分表)
1.4.4.关闭binlog和双1
1.4.5.参数调整....
优化结果:
监控状态良好
参考:
https://www.jianshu.com/p/898d2e4bd3a7
为什么?
(1) 原生态支持TokuDB,另外经过测试环境,5.7要比5.5 版本性能 高 2-3倍
(2) TokuDB:insert数据比Innodb快的多,数据压缩比要Innodb高
(3)监控数据按月份进行切割,为了能够truncate每个分区表,立即释放空间
(4)关闭binlog ----->减少无关日志的记录.
(5)参数调整...----->安全性参数关闭,提高性能.
扩展:部署 zabbix新版+ 新版本 tokudb VS 部署 zabbix + 低版本mariadb
Tokudb特性:
Additional features unique to TokuDB include:
Up to 25x Data Compression
Fast Inserts
Eliminates Slave Lag with Read Free Replication
Hot Schema Changes
Hot Index Creation - TokuDB tables support insertions, deletions and queries with no down time while indexes are being added to that table
Hot column addition, deletion, expansion, and rename - TokuDB tables support insertions, deletions and queries without down-time when an alter table adds, deletes, expands, or renames columns
On-line Backup
参考内容:
https://www.jianshu.com/p/898d2e4bd3a7
https://mariadb.com/kb/en/installing-tokudb/
https://www.percona.com/doc/percona-server/5.7/tokudb/tokudb_installation.html
案例2:
环境: centos 5.8 ,MySQL 5.0版本,MyISAM存储引擎,网站业务(LNMP),数据量50G左右
现象问题: 业务压力大的时候,非常卡;经历过宕机,会有部分数据丢失.
问题分析:
1.MyISAM存储引擎表级锁,在高并发时,会有很高锁等待
2.MyISAM存储引擎不支持事务,在断电时,会有可能丢失数据
职责
1.监控锁的情况:有很多的表锁等待
2.存储引擎查看:所有表默认是MyISAM
解决方案:
- 升级MySQL 5.6.1x版本
- 升级迁移所有表到新环境,调整存储引擎为InnoDB
- 开启双1安全参数
- 重构主从
4. 存储引擎的基本操作
4.1 查看存储引擎
4.1.1 查询支持的存储引擎
mysql> show engines;
4.1.2 查询、设置默认存储引擎
mysql> select @@default_storage_engine;
+--------------------------+
| @@default_storage_engine |
+--------------------------+
| InnoDB |
+--------------------------+
1 row in set (0.00 sec)
vim /etc/my.cnf
default_storage_engine=InnoDB
重启生效。
4.1.3 查看、设定 表的存储引擎
(1) 查看某张表的存储引擎
mysql> show create table xta;
(2) 查询系统中所有业务表的存储引擎信息
mysql> select
table_schema,
table_name ,
engine
from information_schema.tables
where table_schema not in ('sys','mysql','information_schema','performance_schema');
(3)创建表设定存储引擎
mysql> create table xxx (id int) engine=innodb charset=utf8mb4;
(4)修改已有表的存储引擎
mysql> alter table xxx engine=myisam;
mysql> alter table world.xxx engine=innodb;
项目:将所有的非InnoDB引擎的表查询出来,批量修改为InnoDB
mysql> select table_schema,table_name ,engine
from information_schema.tables
where
table_schema not in ('sys','mysql','information_schema','performance_schema')
and engine !='innodb';
mysql> select concat("alter table ",table_schema,".",table_name," engine=innodb;") from information_schema.tables where table_schema not in ('sys','mysql','information_schema','performance_schema') and engine !='innodb' into outfile '/tmp/a.sql';
mysql> source /tmp/a.sql
5. InnoDB 存储引擎的体系结构 ******
5.1 磁盘结构 (on-disk)
5.1.1 表空间结构
介绍: 表空间的概念源于Oracle数据库。最初的目的是为了能够很好的做存储的扩容。
a. 共享(系统)表空间
存储方式
ibdata1~ibdataN, 5.5版本默认的表空间类型 。
ibdata1共享表空间在各个版本的变化
5.5版本:
系统相关:(全局)数据字典信息(表基本结构信息、状态、系统参数、属性..)、UNDO回滚日志(记录撤销操作)、Double Write Buffer信息、临时表信息、change buffer
用户数据: 表数据行、表的索引数据
5.6版本:共享表空间只存储于系统数据,把用户数据独立了,独立表空间管理。
系统相关:(全局)数据字典信息、UNDO回滚信息、Double Write信息、临时表信息、change buffer
5.7版本:在5.6基础上,把临时表独立出来,UNDO也可以设定为独立
系统相关:(全局)数据字典信息、UNDO回滚信息、Double Write信息、change buffer
8.0.11~8.0.19版本:
在5.7的基础上将UNDO回滚信息默认独立,数据字典不再集中存储了。
系统相关:Double Write信息、change buffer
8.0.20版本:在之前版本基础上,独立 Double Write信息
系统相关:change buffer
https://dev.mysql.com/doc/refman/5.7/en/innodb-architecture.html
总结: 对于InnoDB表来讲,例如 city表
city.ibd
city.frm
ibdata1
只是通过cp备份ibd和frm文件无法实现,数据表的恢复
共享表空间管理
扩容共享表空间
mysql> select @@innodb_data_file_path;
+-------------------------+
| @@innodb_data_file_path |
+-------------------------+
| ibdata1:12M:autoextend |
+-------------------------+
1 row in set (0.00 sec)
mysql> select @@innodb_autoextend_increment;
+-------------------------------+
| @@innodb_autoextend_increment |
+-------------------------------+
| 64 |
+-------------------------------+
1 row in set (0.00 sec)
参数用途:ibdata1文件,默认初始大小12M,不够用会自动扩展,默认每次扩展64M
设置方式:
innodb_data_file_path=ibdata1:12M;ibdata2:100M;ibdata3:100M:autoextend
重启数据库报错,查看日志文件
vim /data/3306/data/db01.err
###################
[ERROR] InnoDB: The innodb_system data file './ibdata1' is of a different size 4864 pages (rounded down to MB) than the 768 pages specified in the .cnf file!
###################
实际大小:
4864*16K/1024=76M
my.cnf文件设置:
768*16K/1024=12M
正确的方法:
先查看实际大小:
[root@db01 data]# ls -lh ibdata1
-rw-r----- 1 mysql mysql 76M May 6 17:11 ibdata1
配置文件设定为和实际大小一致:
innodb_data_file_path=ibdata1:76M;ibdata2:100M;ibdata3:100M:autoextend
模拟在初始化时设置共享表空间(生产建议)
5.7 中建议:设置共享表空间2-3个,大小建议1G或者4G,最后一个定制为自动扩展。
8.0 中建议:设置1-2个就ok,大小建议1-4G
# 清理数据
[root@db01 data]# /etc/init.d/mysqld stop
[root@db01 data]# rm -rf /data/3306/data/*
[root@db01 data]# vim /etc/my.cnf
# 修改
innodb_data_file_path=ibdata1:100M;ibdata2:100M;ibdata3:100M:autoextend
# 重新初始化
[root@db01 data]# mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/data/3306/data
# 重启数据库生效
[root@db01 data]# /etc/init.d/mysqld start
b.独立表空间(数据+索引)
介绍
5.6版本开始,针对用户数据,单独的存储管理。存储表的数据行和索引。
8.0 之前: city ---> city.ibd city.frm ibdata1
8.0 之后 city city.ibd
补充:
myisam 表 t1
t1.frm 数据字典
t1.myd 数据行
t1.myi 索引
通过参数控制:
mysql> select @@innodb_file_per_table;
+-------------------------+
| @@innodb_file_per_table |
+-------------------------+
| 1 |
+-------------------------+
测试: 共享表空间存储用户数据
mysql> set global innodb_file_per_table=0;
利用独立表空间进行快速数据迁移
源端:3306/test/t100w -----> 目标端:3307/test/t100w
1. 锁定源端t100w表
mysql> flush tables test.t100w with read lock ;
mysql> show create table test.t100w;
CREATE TABLE `t100w` (
`id` int(11) DEFAULT NULL,
`num` int(11) DEFAULT NULL,
`k1` char(2) DEFAULT NULL,
`k2` char(4) DEFAULT NULL,
`dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 目标端创建test库和t100w空表
mysql> create database test charset=utf8mb4;
CREATE TABLE `t100w` (
`id` int(11) DEFAULT NULL,
`num` int(11) DEFAULT NULL,
`k1` char(2) DEFAULT NULL,
`k2` char(4) DEFAULT NULL,
`dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 单独删除空的表空间文件
mysql> alter table test.t100w discard tablespace;
4. 拷贝源端ibd文件到目标端目录,并设置权限
[root@db01 test]# cp /data/3306/data/test/t100w.ibd /data/3307/data/test/
[root@db01 test]# chown -R mysql.mysql /data/*
5. 导入表空间
mysql> alter table test.t100w import tablespace;
mysql> select count(*) from test.t100w;
+----------+
| count(*) |
+----------+
| 1000000 |
6. 解锁源端数据表
mysql> unlock tables;
彩蛋:
案例1: 利用表空间迁移功能实现数据损坏恢复。
案例背景:
硬件及软件环境:
联想服务器(IBM) ,8核16G
磁盘500G 没有raid
centos 6.8
mysql 5.6.33 innodb引擎 独立表空间
备份没有,日志也没开
开发自用专用库:
jira(bug追踪) 、 confluence(内部知识库) ------>LNMT
故障描述:
断电了,启动完成后根只读
fsck 重启,系统成功启动,mysql启动不了。
结果:confulence库在, jira库不见了。
学员求助内容:
求助:
这种情况怎么恢复?
我问:
有备份没
求助:
连二进制日志都没有,没有备份,没有主从
我说:
没招了,jira需要硬盘恢复了。
求助:
1、jira问题拉倒中关村了
2、能不能暂时把confulence库先打开用着
将生产库confulence,拷贝到1:1虚拟机上/var/lib/mysql,直接访问时访问不了的
问: 有没有工具能直接读取ibd
我说:我查查,最后发现没有
我想出一个办法来:
表空间迁移:
create table xxx
alter table confulence.t1 discard tablespace;
alter table confulence.t1 import tablespace;
虚拟机测试可行。
处理问题思路:
confulence库中一共有107张表。
1、创建107和和原来一模一样的表。
他有2016年的历史库,我让他去他同时电脑上 mysqldump备份confulence库
mysqldump -uroot -ppassw0rd -B confulence --no-data >test.sql
################
万一是自研数据库,怎么办?又没备份,那怎么办?
mysql工具包,mysqlfrm 读取frm文件获得表结构。
################
2、表空间删除。
select concat('alter table ',table_schema,'.'table_name,' discard tablespace;') from information_schema.tables where table_schema='confluence' into outfile '/tmp/discad.sql';
source /tmp/discard.sql
执行过程中发现,有20-30个表无法成功。
主外键关系:很绝望,一个表一个表分析表结构,很痛苦,set foreign_key_checks=0 跳过外键检查。
3、拷贝生产中confulence库下的所有表的ibd文件拷贝到准备好的环境中
select concat('alter table ',table_schema,'.'table_name,' import tablespace;') from information_schema.tables where table_schema='confluence' into outfile '/tmp/import.sql';
4、验证数据
表都可以访问了,数据挽回到了出现问题时刻的状态
课后练习作业:
案例2 : MySQL 5.7 中误删除了ibdata1文件,导致数据无法启动,如何恢复t100w,假设一共100张表,表结构无法通过show create table 获得。
提示:万一是自研数据库,怎么办?又没备份,那怎么办?
mysql工具包,mysqlfrm 读取frm文件获得表结构。
./mysqlfrm /data/3306/data/test/t100w.frm --diagnostic
select concat('alter table ',table_schema,'.'table_name,' discard tablespace;') from information_schema.tables where table_schema='confluence' into outfile '/tmp/discad.sql';
source /tmp/discard.sql
################
undo表空间
1. 作用: 用来作撤销工作。
2. 存储位置: 5.7版本,默认存储在共享表空间中(ibdataN)。8.0版本以后默认就是独立的(undo_001-undo_002)。
3. 生产建议: 5.7版本后,将undo手工进行独立。
4. undo 表空间管理
4.1 如何查看undo的配置参数
SELECT @@innodb_undo_tablespaces; ---->3-5个 #打开独立undo模式,并设置undo的个数。
SELECT @@innodb_max_undo_log_size; #undo日志的大小,默认1G。
SELECT @@innodb_undo_log_truncate; #开启undo自动回收的机制(undo_purge)。
SELECT @@innodb_purge_rseg_truncate_frequency; #触发自动回收的条件,单位是检测次数。
4.2 配置undo表空间
#########官方文档说明############
Important
The number of undo tablespaces can only be configured
when initializing a MySQL instance and is fixed for the life of the instance.
#################################
[root@db01 tmp]# pkill mysqld
[root@db01 tmp]# rm -rf /data/3357/data/*
vim /etc/my.cnf
# 添加参数
innodb_undo_tablespaces=3
innodb_max_undo_log_size=128M
innodb_undo_log_truncate=ON
innodb_purge_rseg_truncate_frequency=32
# 重新初始化数据库生效=
[root@db01 data]# /usr/local/mysql57/bin/mysqld --defaults-file=/data/3357/my.cnf --initialize-insecure --user=mysql --basedir=/usr/local/mysql57 --datadir=/data/3357/data
# 启动数据库
[root@db01 data]# /etc/init.d/mysqld start
[root@db01 data]# ll /data/3306/data/undo00*
-rw-r----- 1 mysql mysql 10485760 May 7 15:39 /data/3306/data/undo001
-rw-r----- 1 mysql mysql 10485760 May 7 15:39 /data/3306/data/undo002
-rw-r----- 1 mysql mysql 10485760 May 7 15:39 /data/3306/data/undo003
#如果进行undo独立存储到其他文件系统
a. 关闭数据库:
[root@db01 data]# systemctl start mysqld3357
b.设定路径参数
innodb_undo_directory=/data/3357/undologs
c. 创建目录,并拷贝文件
[root@db01 data]# systemctl stop mysqld3357
mkdir -p /data/3357/undologs
chown -R mysql. /data/*
cp -a /data/3357/data/undo* /data/3357/undologs
# 注: 8.0 undo表空间与5.7稍有区别,可参考:
https://dev.mysql.com/doc/refman/8.0/en/innodb-undo-tablespaces.html
临时表空间
1. 作用: 存储临时表。
2. 管理:
innodb_temp_data_file_path=ibtmp1:12M;ibtmp2:128M:autoextend:max:500M
重启生效。
3. 建议数据初始化之前设定好,一般2-3个,大小512M-1G。
5.1.2 InnoDB 事务日志
redo log 重做日志
1. 作用: 记录内存数据页的变化。实现“前进”的功能。WAL(write ahead log),MySQL保证redo优先于数据写入磁盘。
2. 存储位置: 数据路径下,进行轮序覆盖记录日志
ib_logfile0 48M
ib_logfile1 48M
3. 管理:
3.1 查询redo log文件配置
mysql> show variables like '%innodb_log_file%';
+---------------------------+----------+
| Variable_name | Value |
+---------------------------+----------+
| innodb_log_file_size | 50331648 |
| innodb_log_files_in_group | 2 |
+---------------------------+----------+
3.2 设置
生产建议:
大小: 512M-4G
组数: 2-4组
vim /etc/my.cnf
# 添加参数:
innodb_log_file_size=100M
innodb_log_files_in_group=3
#重启生效
[root@db01 data]# /etc/init.d/mysqld restart
[root@db01 data]# ll /data/3306/data/ib_logfile*
-rw-r----- 1 mysql mysql 104857600 May 7 16:17 /data/3306/data/ib_logfile0
-rw-r----- 1 mysql mysql 104857600 May 7 16:17 /data/3306/data/ib_logfile1
-rw-r----- 1 mysql mysql 104857600 May 7 16:17 /data/3306/data/ib_logfile2
[root@db01 data]#
undo log 回滚日志
略。
5.1.3 其他结构
ib_buffer_pool 预热文件
作用:
缓冲和缓存,用来做“热”(经常查询或修改)数据页,减少物理IO。
当关闭数据库的时候,缓冲和缓存会失效。
5.7版本中,MySQL正常关闭时,会将内存的热数据存放(流方式)至ib_buffer_pool。下次重启直接读取ib_buffer_pool加载到内存中。
Double Write Buffer(DWB)
(8.0.19之前 默认在ibdataN中,8.0.20以后可以独立了。)
作用:
MySQL,最小IO单元page(16KB),OS中最小的IO单元是block(4KB)
为了防止出现以下问题:
mysqld process crash in the middle of a page write
5.2 内存结构
5.2.1 InnoDB BUFFER POOL(IBP)
作用:
用来缓冲、缓存,MySQL的数据页和索引页。MySQL中最大的、最重要的内存区域。
管理:
mysql> select @@innodb_buffer_pool_size;
默认大小: 128M
生产建议: 物理内存的:50-80%。
在线设置:
mysql> set global innodb_buffer_pool_size=268435456;
重新登录mysql生效。
永久设置:
vim /etc/my.cnf
添加参数
innodb_buffer_pool_size=256M
重启生效
5.2.2 InnoDB LOG BUFFER (ILB)
作用: 用来缓冲 redo log日志信息。
管理 :
mysql> select @@innodb_log_buffer_size;
默认大小:16M
生产建议:和innodb_log_file_size有关,1-N倍
设置方式 :
vim /etc/my.cnf
innodb_log_buffer_size=33554432
重启生效:
[root@db01 data]# /etc/init.d/mysqld restart
6. InnoDB核心特性--事务支持
6.1 介绍
事务:Transaction (交易)。 伴随着交易类的业务出现的概念(工作模式)
交易?
物换物,等价交换。
货币换物,等价交换。
虚拟货币换物(虚拟物品),等价交换。
现实生活中怎么保证交易“和谐” ,法律、道德等规则约束。
数据库中为了保证线上交易的“和谐”,加入了“事务”工作机制。
6.2 事务ACID特性
A: 原子性
一个事务生命周期中的DML语句,要么全成功要么全失败,不可以出现中间状态。
begin;DML1;DML2;DML3;commit;
C:一致性
事务发生前,中,后,数据都最终保持一致。
CR + double write
I:隔离性
事务操作数据行的时候,不会受到其他时候的影响。
D: 持久性
一但事务提交,永久生效(落盘)。
6.3 事务的生命周期管理
6.3.1 标准(显示)的事务控制语句
# 开启事务
begin;
# 提交事务
commit;
# 回滚事务
rollback;
注意:
事务生命周期中,只能使用DML语句(select、update、delete、insert)
# 事务的生命周期演示:
mysql> use world
mysql> begin;
mysql> delete from city where id=1;
mysql> update city set countrycode='CHN' where id=2;
mysql> commit;
mysql> begin;
mysql> select * from city limit 10;
mysql> update city set countrycode='AFG' where id=2;
mysql> delete from city where id=3;
mysql> rollback;
6.3.2 MySQL的自动提交机制(autocommit)
参数:
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
作用:
在没有显示的使用begin语句的时候,执行DML,会在DML前自动添加begin,并在DML执行后自动添加commit。
建议: 频繁事务业务场景中,关闭autocommit,或者每次事务执行时都是显示的begin和commit;
关闭方法:
# 临时:
mysql> set global autocommit=0;
退出会话,重新连接配置生效。
# 永久:
[root@db01 ~]# vim /etc/my.cnf
autocommit=0
重启生效。
6.3.3 隐式提交和回滚
begin;
DML1;
DML2;
DML3;
commit;
# 隐式提交情况
begin
a
b
SET AUTOCOMMIT = 1
导致提交的非事务语句:
DDL语句: (ALTER、CREATE 和 DROP)
DCL语句: (GRANT、REVOKE 和 SET PASSWORD)
锁定语句:(LOCK TABLES 和 UNLOCK TABLES)
导致隐式提交的语句示例:
TRUNCATE TABLE
LOAD DATA INFILE
SELECT FOR UPDATE
# 隐式回滚
会话窗口被关闭。
数据库关闭 。
出现事务冲突(死锁)。
6.4 事务的隔离级别
6.4.1 作用
实现事务工作期间的“读”的隔离
读? ----》 数据页的读
6.4.2 级别类型
mysql> select @@transaction_isolation;
# RU : READ-UNCOMMITTED 读未提交
可以读取到事务未提交的数据。隔离性差,会出现脏读(当前内存读),不可重复读,幻读问题
# RC : READ-COMMITTED 读已提交(可以用):
可以读取到事务已提交的数据。隔离性一般,不会出现脏读问题,但是会出现不可重复读,幻读问题
# RR : REPEATABLE-READ 可重复读(默认) :
防止脏读(当前内存读),防止不可重复读,会出现幻读问题
# SR : SERIALIZABLE 可串行化
结论: 隔离性越高,事务的并发度就越差。
6.4.3 脏读
设置隔离级别为:'READ-UNCOMMITTED'
mysql> set global transaction_isolation='READ-UNCOMMITTED';
mysql> set global autocommit=0;
重新开启两个session;
mysql> select @@transaction_isolation;
mysql> select @@autocommit;
创建测试表
create table t1 (
id int not null primary key auto_increment ,
a int not null ,
b varchar(20) not null,
c varchar(20) not null
)charset=utf8mb4 engine=innodb;
begin;
insert into t1(a,b,c)
values
(5,'a','aa'),
(7,'c','ab'),
(10,'d','ae'),
(13,'g','ag'),
(14,'h','at'),
(16,'i','au'),
(20,'j','av'),
(22,'k','aw'),
(25,'l','ax'),
(27,'o','ay'),
(31,'p','az'),
(50,'x','aza'),
(60,'y','azb');
commit;
6.4.3 不可重复读
mysql> set global transaction_isolation='READ-COMMITTED';
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED |
6.4.3 幻读
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED |
6.5 事务的工作流程原理(ACID特性如何保证)
6.5.1 名词介绍
# 重做日志 (redo log)
ib_logfile0~N 48M , 轮询使用
# 日志缓冲区
redo log buffer : redo内存区域
# 表空间数据文件
ibd : 存储数据行和索引
# 数据缓冲区
InnoDB buffer pool : 缓冲区池,数据和索引的缓冲
# 日志序列号
LSN
磁盘数据页(ibd文件的page),redo log文件(ib_logfile),Innodb_buffer_pool中的数据页,redo buffer
MySQL 每次数据库启动,都会比较磁盘数据页和redolog的LSN,必须要求两者LSN一致数据库才能正常启动
#WAL : Write Ahead Log
Redo日志优先于数据页写到磁盘。
# 脏页: Dirty Page
内存脏页,内存中发生了修改,没写入到磁盘之前,我们把内存页称之为脏页.
# CheckPoint
CKPT:检查点,就是将脏页刷写到磁盘的动作
# DB_TRX_ID(6字节) 事务ID号
InnoDB会为每一个事务生成一个事务号,伴随着整个事务生命周期.
# DB_ROLL_PTR(7字节) 回滚指针
rollback 时,会使用 undo 日志回滚已修改的数据。DB_ROLL_PTR指向了此次事务的回滚位置点,用来找到undo日志信息。
6.5.2 事务工作流程原理
事务举例:
begin;
update t1 set A=2 where A=1;
commit;
# redo log 重做日志如何应用
1. 用户发起update事务语句,将磁盘数据页(page100,A=1,LSN=1000)加载到内存(buffer_pool)缓冲区。
2. 在内存中发生数据页修改(A=1改成A=2),形成脏页,更改中数据页的变化,记录到redo buffer中,加入1000个字节日志。LSN=1000+1000=2000。
3. 当commit语句执行时,基于WAL机制,等到redo buffer中的日志完全落盘到ib_logfileN中,commit正式完成。
4. ib_logfileN中记录了一条日志。内容:page100数据页变化+LSN=2000。
## 情景: 当此时,redo落地了,数据页没有落地,宕机了。
1. MySQL CR(自动故障恢复)工作模式,启动数据库时,自动检查redo的LSN和数据页LSN。
2. 如果发现redoLSN>数据页的LSN ,加载原始数据页+变化redo指定内存。使用redo重构脏页(前滚)。
3. 如果确认此次事务已经提交(commit标签),立即触发CKPT动作,将脏页刷写到磁盘上。
## 补充一点:
MySQL有一种机制,批量刷写redo的机制。会在A事务commit时,顺便将redo buffer中的未提交的redo日志也一并刷到磁盘。
为了区分不同状态的redo,日志记录时,会标记是否COMMIT。
## redo保证了ACID哪些特性?
主要是D的特性,另外A、C也有间接关联。
# undo log 回滚日志如何应用?
1. 事务发生数据页修改之前,会申请一个undo事务操作,保存事务回滚日志(逆向操作的逻辑日志)。
2. undo写完之后,事务修改数据页头部(会记录DB_TRX_ID+DB_ROLL_PTR),这个信息也会被记录的redo。
情景1:
当执行rollback命令时。根据数据页的DB_TRX_ID+DB_ROLL_PTR信息,找到undo日志,进行回滚。
情景2:
begin;
update t1 set A=2 where A=1;
宕机。
假设: undo 有 , redo没有
启动数据库时,检查redo和数据页的LSN号码。发现是一致的。
所以不需要进行redo的前滚,此时也不需要回滚。undo信息直接被标记为可覆盖状态。
假设:undo 有,redo也有(没有commit标签。)
1. MySQL CR(自动故障恢复)工作模式,启动数据库时,自动检查redo的LSN和数据页LSN。
2. 如果发现redoLSN>数据页的LSN ,加载原始数据页+变化redo指定内存。使用redo重构脏页(前滚)。
3. 如果确认此次事务没有commit标记,立即触发回滚操作,根据DB_TRX_ID+DB_ROLL_PTR信息,找到und回滚日志,实现回滚。
以上流程被称之为InnoDB的核心特性:自动故障恢复(Crash Recovery)。先前滚再回滚,先应用redo再应用undo。
## undo在ACID中保证了啥?
主要保证事务的A的特性,同时C和I的特性也有关系。
6.5.3 事务中的C特性怎么保证?
InnoDB crash recovery:数据库意外宕机时刻,通过redo前滚+undo回滚保证数据的最终一致。
InnoDB doublewrite buffer: 默认存储在ibdataN中。解决数据页写入不完整
mysqld process crash in the middle of a page write, InnoDB can find a good copy of the page from the doublewrite buffer during crash recovery.
DWB一共2M。分两次,每次1M写入
6.5.4 事务中的I的特性怎么保证?
# 隔离级别:读隔离性
RU : 脏读 、 不可重复读 、幻读
RC : 不可重复读、幻读
RR :有可能会出现幻读。
SR(SE) :事务串行工作。
# 锁机制:写的隔离
作用:保护并发访问资源。
保护的资源分类:
latch(闩锁):rwlock、mutex,主要保护内存资源
MDL: Metadata_lock,元数据(DDL操作)
table_lock: 表级别
lock table t1 read ;
mysqldump、XBK(PBK):备份非InnoDB数据时,触发FTWRL全局锁表(Global)。
行锁升级为表锁。
row lock:InnoDB 默认锁粒度,加锁方式都是在索引加锁的。
record lock : 记录锁,在聚簇索引锁定。RC级别只有record lock。
gap lock : 间隙锁,在辅助索引间隙加锁。RR级别存在。防止幻读。
next lock : 下一键锁, GAP+Record。 RR级别存在。防止幻读。
什么是幻读,RR又是如何防止幻读?
RC级别下不可重读现象演示:
vim /etc/my.cnf
#添加隔离级别参数:
transaction_isolation=READ-COMMITTED
#重启数据库
[root@db01 ~]# /etc/init.d/mysqld restart
打开两个会话窗口:
sessionA:
第一步:
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED |
+-------------------------+
1 row in set (0.00 sec)
第三步:
mysql> create database test charset utf8mb4;
mysql> use test;
mysql> create table t1 (id int primary key auto_increment,num int not null , name varchar(20) not null);
mysql> insert into t1(num,name) values(1,'a'),(3,'c'),(6,'d'),(7,'x');
mysql> insert into t1(num,name) values(11,'a'),(23,'c'),(36,'d'),(37,'x'');
mysql> insert into t1(num,name) values(51,'as'),(63,'hc'),(76,'ds'),(87,'x','xyz');
mysql> commit;
mysql> select * from t1;
+----+-----+------+
| id | num | name |
+----+-----+------+
| 1 | 1 | a |
| 2 | 3 | c |
| 3 | 6 | d |
| 4 | 7 | x |
| 5 | 11 | a |
| 6 | 23 | c |
| 7 | 36 | d |
| 8 | 37 | x |
| 9 | 51 | as |
| 10 | 63 | hc |
| 11 | 76 | ds |
| 12 | 87 | xyz |
+----+-----+------+
第五步:
mysql> begin;
第七步:
mysql> update t1 set name='aa' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
=================================================
sessinB:
第二步:
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED |
+-------------------------+
1 row in set (0.00 sec)
第四步:
mysql> use test;
mysql> select * from test.t1;
+----+-----+------+
| id | num | name |
+----+-----+------+
| 1 | 1 | a |
| 2 | 3 | c |
| 3 | 6 | d |
| 4 | 7 | x |
| 5 | 11 | a |
| 6 | 23 | c |
| 7 | 36 | d |
| 8 | 37 | x |
| 9 | 51 | as |
| 10 | 63 | hc |
| 11 | 76 | ds |
| 12 | 87 | xyz |
+----+-----+------+
12 rows in set (0.00 sec)
第六步:
mysql> begin;
mysql> select * from t1 where id=1;
+----+-----+------+
| id | num | name |
+----+-----+------+
| 1 | 1 | a |
+----+-----+------+
1 row in set (0.00 sec)
第八步:
mysql> select * from t1 where id=1;
+----+-----+------+
| id | num | name |
+----+-----+------+
| 1 | 1 | aa |
+----+-----+------+
1 row in set (0.00 sec)
==================================
RC级别下幻读现象演示:
准备工作:
mysql> alter table t1 add index(num);
[root@db01 ~]# mysqldump test t1 >/tmp/t1.sql
session A :
第一步:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+----+-----+------+
| id | num | name |
+----+-----+------+
| 1 | 1 | aa |
| 2 | 3 | c |
| 3 | 6 | d |
| 4 | 7 | x |
| 5 | 11 | a |
| 6 | 23 | c |
| 7 | 36 | d |
| 8 | 37 | x |
| 9 | 51 | as |
| 10 | 63 | hc |
| 11 | 76 | ds |
| 12 | 87 | xyz |
+----+-----+------+
12 rows in set (0.00 sec)
第三步:
mysql> update t1 set num=10 where num<10;
Query OK, 4 rows affected (0.00 sec)
Rows matched: 4 Changed: 4 Warnings: 0
第五步:
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+----+-----+------+
| id | num | name |
+----+-----+------+
| 1 | 10 | aa |
| 2 | 10 | c |
| 3 | 10 | d |
| 4 | 10 | x |
| 5 | 11 | a |
| 6 | 23 | c |
| 7 | 36 | d |
| 8 | 37 | x |
| 9 | 51 | as |
| 10 | 63 | hc |
| 11 | 76 | ds |
| 12 | 87 | xyz |
| 13 | 5 | aaa |
+----+-----+------+
Session B:
第二步:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+----+-----+------+
| id | num | name |
+----+-----+------+
| 1 | 1 | aa |
| 2 | 3 | c |
| 3 | 6 | d |
| 4 | 7 | x |
| 5 | 11 | a |
| 6 | 23 | c |
| 7 | 36 | d |
| 8 | 37 | x |
| 9 | 51 | as |
| 10 | 63 | hc |
| 11 | 76 | ds |
| 12 | 87 | xyz |
+----+-----+------+
12 rows in set (0.00 sec)
第四步:
mysql> insert into t1(num,name) values(5,'aaa');
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
# 功能性上:
IS : select * from t1 lock in shared mode;
S : 读锁。
IX : 意向排他锁。表上添加的。 select * from t1 for update;
X : 排他锁,写锁。
# MVCC : 多版本并发控制
乐观锁: 乐观。
悲观锁: 悲观。
每个事务操作都要经历两个阶段:
读: 乐观锁。
MVCC利用乐观锁机制,实现非锁定读取。
read view:RV,版本号集合。
trx1 :
begin;
dml1 ---> 在做第一个查询的时候,当前事务,获取系统最新的:RV1 版本快照。
dml2 ---> 生成 RV2 版本快照。
select 查询 RV2 快照数据
commit; ----> RV2 快照数据 ----》系统最新快照。
RC
trx1: Rv1 Rv2 commit;
trx2 RVV1 RVV1 RV2
RR
trx1 : 第一个查询时, 生成global consitence snapshot RV-CS1(10:00) ,一直伴随着事务生命周期结束。
trx2 : 第一个查询时,生成global consitence snapshot RV-CS2(10:01) ,一直伴随着事务生命周期结束。
快照技术由undo log来提供。
写: 悲观锁 X
总结:
1. MVCC采用乐观锁机制,实现非锁定读取。
2. 在RC级别下,事务中可以立即读取到其他事务commit过的readview
3. 在RR级别下,事务中从第一次查询开始,生成一个一致性readview,直到事务结束。