InnoDB底层原理与日志体系

2.2 MySql执行过程

2.2.1 MySQL内部结构

InnoDB底层原理与日志体系_第1张图片
总体来说MySQL分为Server层和存储引擎层;
Server层:
主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图、binlog日志等;
存储引擎层:
负责数据的存储和提取,其架构模式是插件式的,支持InnoDB、MyISAM、Memory、BDB等,最常用的是InnoDB,MySQL自带的表也有用MyISAM的,MySQL5.5.5默认存储引擎为InnoDB,即创建表时不指定存储引擎默认为InnoDB;

  • 客户端

比如应用程序、DG数据库工具;

  • 连接器

连接器负责跟客户端建立连接(长连接)、获取权限、维持和管理链接;
在权限表里查找当前用户拥有的权限,一个用户连接成功后,即使修改了该用户的权限,不会影响已存在连接的权限;
连接命令:

[root@192 ~]# mysql -h host[数据库地址] -u root[用户] -p root[密码] -P 3306
  • 查询缓存

查询缓存是以key-value的形式存放在内存中的,key为sql语句,value为结果集;
相同的sql会命中缓存,直接将结果集返回给客户端,没有命中缓存则继续走后面的执行流程,查询结果会存入缓存中;
实际应用中建议关闭缓存,实际应用中的sql大概率不是相同的,命中的缓存概率底,空间换时间不划算,对一个表更新后将会清空该表上的所有缓存。
静态表可以使用缓存,静态表是指读多写少的表;
5.7默认关闭缓存,5.8去掉了缓存;

my.cnf
#query_cache_type有3个值 0代表关闭查询缓存OFF,1代表开启ON,2(DEMAND)代表当sql语句中有SQL_CACHE关键词时才缓存
query_cache_type=2

query_cache_type=2默认的sql查询不走缓存,当执行带SQL_CACHE关键字时才使用缓存:

mysql> select SQL_CACHE * from test where ID=5

查看当前mysql实例是否开启缓存机制:

mysql> show global variables like "%query_cache_type%";

结果:
InnoDB底层原理与日志体系_第2张图片

  • 分析器

分为词法分析和语法分析;
词法分析:MySQL会根据空格来拆分关键字,比如识别查询、表名、字段等;
语法分析:MySQL会检查sql语句是否符合规范,否则会报错You have an error in your SQL syntax

  • 优化器

join的连表顺序,使用哪个索引,还有MySQL内部的一些优化机制;

  • 执行器

执行之前先判断该用户对表是否有对应的权限,没有权限则返回错误,有权限则调用表对应引擎提供的接口;
命中缓存时,在返回结果的时候做权限校验;

  • 存储引擎

存储数据,例如InnoDB MyIsAm Meomery;

2.2.2 MySQL日志体系

(1)redo log
redolog是InnoDB特有的日志,两阶段提交,使得MySQL有cash-safe能力;
redolog记录的是物理修改,对应的哪一页修改了什么;
redolog是顺序写的,速度非常快;

  • redolog相关参数

innodb_log_buffer_size:设置redo log buffer大小参数,默认16M ,最大值是4096M,最小值为1M。

show variables like '%innodb_log_buffer_size%';

innodb_log_group_home_dir:设置redo log文件存储位置参数,默认值为"./",即innodb数据文件存储位置,其中的 ib_logfile0 和 ib_logfile1 即为redo log文件。

show variables like '%innodb_log_group_home_dir%';

innodb_log_files_in_group:设置redo log文件的个数,命名方式如: ib_logfile0, iblogfile1… iblogfileN。默认2个,最大100个。

show variables like '%innodb_log_files_in_group%';

innodb_log_file_size:设置单个redo log文件大小,默认值为48M。最大值为512G,注意最大值指的是整个 redo log系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size)不能大于最大值512G。

show variables like '%innodb_log_file_size%';
  • redo log 写入磁盘过程分析

redo log 从头开始写,写完一个文件继续写另一个文件,写到最后一个文件末尾就又回到第一个文件开头循环写,如下面这个图所示。
InnoDB底层原理与日志体系_第3张图片
write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。
checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件里。
write pos 和 checkpoint 之间的部分就是空着的可写部分,可以用来记录新的操作。如果 write pos 追上checkpoint,表示redo log写满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。
innodb_flush_log_at_trx_commit:这个参数控制 redo log 的写入策略,它有三种可能取值:

设置为0:表示每次事务提交时都只是把 redo log 留在 redo log buffer 中,数据库宕机可能会丢失数据。
设置为1(默认值):表示每次事务提交时都将 redo log 直接持久化到磁盘,数据最安全,不会因为数据库宕机丢失数据,但是效率稍微差一点,线上系统推荐这个设置。
设置为2:表示每次事务提交时都只是把 redo log 写到操作系统的缓存page cache里,这种情况如果数据库宕机是不会丢失数据的,但是操作系统如果宕机了,page cache里的数据还没来得及写入磁盘文件的话就会丢失数据。

InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 操作系统函数 write 写到文件系统的 page cache,然后调用操作系统函数 fsync 持久化到磁盘文件。
redo log写入策略参看下图:
InnoDB底层原理与日志体系_第4张图片

# 查看innodb_flush_log_at_trx_commit参数值:
show variables like 'innodb_flush_log_at_trx_commit';
# 设置innodb_flush_log_at_trx_commit参数值(也可以在my.ini或my.cnf文件里配置):
set global innodb_flush_log_at_trx_commit=1;  

(2)undo log
InnoDB对undo log文件的管理采用段的方式,也就是回滚段(rollback segment) 。每个回滚段记录了 1024 个 undo log segment ,每个事务只会使用一个undo log segment。
在MySQL5.5的时候,只有一个回滚段,那么最大同时支持的事务数量为1024个。在MySQL 5.6开始,InnoDB支持最大128个回滚段,故其支持同时在线的事务限制提高到了 128*1024 。

innodb_undo_directory:设置undo log文件所在的路径。该参数的默认值为"./",即innodb数据文件存储位置,目录下ibdata1文件就是undo log存储的位置。
innodb_undo_logs: 设置undo log文件内部回滚段的个数,默认值为128。
innodb_undo_tablespaces: 设置undo log文件的数量,这样回滚段可以较为平均地分布在多个文件中。设置该参数后,会在路径innodb_undo_directory看到undo为前缀的文件。
  • undo log日志什么时候删除

新增类型的,在事务提交之后就可以清除掉了。
修改类型的,事务提交之后不能立即清除掉,这些日志会用于mvcc。只有当没有事务用到该版本信息时才可以清除。
(3)bin log

  • binlog相关参数

binlog二进制日志记录保存了所有执行过的修改操作语句,不保存查询操作。如果 MySQL 服务意外停止,可通过二进制日志文件排查,用户操作或表结构操作,从而来恢复数据库数据。
启动binlog记录功能,会影响服务器性能,但如果需要恢复数据或主从复制功能,则好处则大于对服务器的影响。

# 查看binlog相关参数
show variables like '%log_bin%';

结果(对应的是mysql阿里云云数据库5.7版本,可能跟自己安装的mysql5.7不太一样):
InnoDB底层原理与日志体系_第5张图片

log_bin:binlog日志是否打开状态
log_bin_basename:是binlog日志的基本文件名,后面会追加标识来表示每一个文件,binlog日志文件会滚动增加
log_bin_index:指定的是binlog文件的索引文件,这个文件管理了所有的binlog文件的目录。
sql_log_bin:sql语句是否写入binlog文件,ON代表需要写入,OFF代表不需要写入。如果想在主库上执行一些操作,但不复制到slave库上,可以通过修改参数sql_log_bin来实现。比如说,模拟主从同步复制异常。

MySQL5.7 版本中,binlog默认是关闭的,8.0版本默认是打开的。log_bin的值是OFF就代表binlog是关闭状态,打开binlog功能,需要修改配置文件my.ini(windows)或my.cnf(linux),然后重启数据库。
在配置文件中的[mysqld]部分增加如下配置:

# log-bin设置binlog的存放位置,可以是绝对路径,也可以是相对路径,这里写的相对路径,则binlog文件默认会放在data数据目录下
log-bin=mysql-binlog
# Server Id是数据库服务器id,随便写一个数都可以,这个id用来在mysql集群环境中标记唯一mysql服务器,集群环境中每台mysql服务器的id不能一样,不加启动会报错
server-id=1
# 其他配置
binlog_format = row # 日志文件格式,下面会详细解释
expire_logs_days = 15 # 执行自动删除距离当前15天以前的binlog日志文件的天数, 默认为0, 表示不自动删除
max_binlog_size = 200M # 单个binlog日志文件的大小限制,默认为 1GB

重启数据库后我们再去看data数据目录会多出两个文件,第一个就是binlog日志文件,第二个是binlog文件的索引文件,这个文件管理了所有的binlog文件的目录。
InnoDB底层原理与日志体系_第6张图片
当然也可以执行命令查看有多少binlog文件:

show binary logs;

结果:
InnoDB底层原理与日志体系_第7张图片

  • binlog 的日志格式

用参数 binlog_format 可以设置binlog日志的记录格式,mysql支持三种格式类型:
STATEMENT:基于SQL语句的复制,每一条会修改数据的sql都会记录到master机器的bin-log中,这种方式日志量小,节约IO开销,提高性能,但是对于一些执行过程中才能确定结果的函数,比如UUID()、SYSDATE()等函数如果随sql同步到slave机器去执行,则结果跟master机器执行的不一样。
ROW:基于行的复制,日志中会记录成每一行数据被修改的形式,然后在slave端再对相同的数据进行修改记录下每一行数据修改的细节,可以解决函数、存储过程等在slave机器的复制问题,但这种方式日志量较大,性能不如Statement。举个例子,假设update语句更新10行数据,Statement方式就记录这条update语句,Row方式会记录被修改的10行数据。
MIXED:混合模式复制,实际就是前两种模式的结合,在Mixed模式下,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种,如果sql里有函数或一些在执行时才知道结果的情况,会选择Row,其它情况选择Statement,推荐使用这一种

  • binlog写入磁盘机制

binlog写入磁盘机制主要通过 sync_binlog 参数控制,默认值是 0。
为0的时候,表示每次提交事务都只 write 到page cache,由系统自行判断什么时候执行 fsync 写入磁盘。虽然性能得到提升,但是机器宕机,page cache里面的 binlog 会丢失。
也可以设置为1,表示每次提交事务都会执行 fsync 写入磁盘,这种方式最安全。
还有一种折中方式,可以设置为N(N>1),表示每次提交事务都write 到page cache,但累积N个事务后才 fsync 写入磁盘,这种如果机器宕机会丢失N个事务的binlog。

  • 发生以下任何事件时, binlog日志文件会重新生成(是指新增日志文件,老的日志文件仍保留):

服务器启动或重新启动;
服务器刷新日志,执行命令flush logs
日志文件大小达到 max_binlog_size 值,默认值为 1GB;

  • 删除 binlog 日志文件
删除当前的binlog文件
reset master;
# 删除指定日志文件之前的所有日志文件,下面这个是删除6之前的所有日志文件,当前这个文件不删除
purge master logs to 'mysql-binlog.000006';
# 删除指定日期前的日志索引中binlog日志文件
purge master logs before '2023-01-21 14:00:00';
  • 查看 binlog 日志文件

可以用mysql自带的命令工具 mysqlbinlog 查看binlog日志内容

# 查看bin-log二进制文件(命令行方式,不用登录mysql)
mysqlbinlog --no-defaults -v --base64-output=decode-rows D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000007 

# 查看bin-log二进制文件(带查询条件)
mysqlbinlog --no-defaults -v --base64-output=decode-rows D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000007 start-datetime="2023-01-21 00:00:00" stop-datetime="2023-02-01 00:00:00" start-position="5000" stop-position="20000"

binlog日志文件里有具体的修改伪sql语句及执行时的相关情况;

  • binlog日志文件恢复数据

在binlog日志里找到要恢复修改sql的位置标识(#at后面的数值),执行以下语句

mysqlbinlog  --no-defaults --start-position=219 --stop-position=701 --database=test D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000009 | mysql -uroot -p123456 -v test

# 补充一个根据时间来恢复数据的命令,我们找到第一条sql BEGIN前面的时间戳标记 SET TIMESTAMP=1674833544,再找到第二条sql COMMIT后面的时间戳标记 SET TIMESTAMP=1674833663,转成datetime格式
mysqlbinlog  --no-defaults --start-datetime="2023-1-27 23:32:24" --stop-datetime="2023-1-27 23:34:23" --database=test D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000009 | mysql -uroot -p123456 -v test

最佳实践:
每天凌晨之后备份全量数据库,通过备份数据和当天的binlog日志来恢复数据,binlog保留时间比备份频率稍大些(比如3天),防止备份失败导致数据丢失;

mysqldump -u root 数据库名>备份文件名;   #备份整个数据库
mysqldump -u root 数据库名 表名字>备份文件名;  #备份整个表

mysql -u root test < 备份文件名 #恢复整个数据库,test为数据库名称,需要自己先建一个数据库test
  • 为什么会有redo log和binlog两份日志呢?

因为最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。
(4)错误日志
Mysql还有一个比较重要的日志是错误日志,它记录了数据库启动和停止,以及运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。
在MySQL数据库中,错误日志功能是默认开启的,而且无法被关闭。

# 查看错误日志存放位置
show variables like '%log_error%';

(5)通用查询日志
通用查询日志记录用户的所有操作,包括启动和关闭MySQL服务、所有用户的连接开始时间和截止时间、发给 MySQL 数据库服务器的所有 SQL 指令等,如select、show等,无论SQL的语法正确还是错误、也无论SQL执行成功还是失败,MySQL都会将其记录下来。
通用查询日志用来还原操作时的具体场景,可以帮助我们准确定位一些疑难问题,比如重复支付等问题。
general_log:是否开启日志参数,默认为OFF,处于关闭状态,因为开启会消耗系统资源并且占用磁盘空间。一般不建议开启,只在需要调试查询问题时开启。
general_log_file:通用查询日志记录的位置参数。

show variables like '%general_log%';
# 打开通用查询日志
SET GLOBAL general_log=on;

2.2.3 MySQL全局参数配置

配置文件my.ini或my.cnf的全局参数;

[mysqld]
max_connections=3000 # 连接的创建和销毁都需要系统资源,比如内存、文件句柄,连接数过高,不一定带来吞吐量的提高,而且可能占用更多的系统资源。
max_user_connections=2980 # 允许用户连接的最大数量,剩余连接数用作DBA管理。
back_log=300 # MySQL能够暂存的连接数量。如果MySQL的连接数达到max_connections时,新的请求将会被存在堆栈中,等待某一连接释放资源,该堆栈数量即back_log,如果等待连接的数量超过back_log,将被拒绝。
wait_timeout=300 # 客户端(比如navicat)通过jdbc连接mysql服务端后没有操作多久后自动断开,默认28800,单位秒,即8个小时。
interactive_timeout=300 # mysql client(linux命令行登录)连接mysql服务端后没有操作多久后自动断开,默认28800,单位秒,即8个小时。
innodb_thread_concurrency=64 # 此参数用来设置innodb线程的并发数,默认值为0表示不被限制,若要设置则与服务器的CPU核心数相同或是CPU的核心数的2倍,如果超过配置并发数,则需要排队,这个值不宜太大,不然可能会导致线程之间锁争用严重,影响性能。
innodb_buffer_pool_size=40G # innodb存储引擎buffer pool缓存大小,一般为物理内存的60%-70%。
innodb_lock_wait_timeout=10 # 行锁锁定时间,默认50s,根据公司业务定,没有标准值。
innodb_flush_log_at_trx_commit=1
sync_binlog=1
sort_buffer_size=4M # 每个需要排序的线程分配该大小的一个缓冲区。增加该值可以加速ORDER BY 或 GROUP BY操作。session级别的参数;
join_buffer_size=4M # 用于表关联缓存的大小,和sort_buffer_size一样,该参数对应的分配内存也是每个连接独享;

你可能感兴趣的:(#,MySQL,mysql,InnoDB,日志体系)