之前我们提过msyql中的库、表、记录行与我们自己操作的文件夹、文件、文件行的对应关系
库 -------------> 文件夹
表 -------------> 文件
row(记录行) ---> 文件中的一行内容
当时我们为了方便理解,对于数据库中的一张表
# 库:db1
use db1;
# 表:t1
create table t1(id int,name varchar(16),age int);
# 记录行
insert t1 values
(1,"egon",18),
(2,"tom",19),
(3,"jack",20);
mysql> select * from t1;
+------+------+------+
| id | name | age |
+------+------+------+
| 1 | egon | 18 |
| 2 | tom | 19 |
| 3 | jack | 20 |
+------+------+------+
3 rows in set (0.00 sec)
我们可以理解成,在db1文件夹下有一个文本文件,文件中有三行内容
事实上,mysql的存储引擎中关于表中数据的存储结构要复杂的多
InnoDB存储引擎的逻辑存储结构和 Oracle大致相同 ,所有数据都被逻辑地存放在一个空间中 ,我们称之为表空间 ( tablespace ) ,表空间又由:段 ( segment ) 、区 ( extent ) 、页 ( page ) 组成 。页在一些文档中有时也称为块(block)或磁盘块,一次io操作的是一个磁盘的数据,即一页数据。
InnoDB存储引擎的逻辑存储结构大致如下图所示
详解如下
Row行
一个Row存放的是一行内容,有trx id,回滚指针,该行包含的n列内容
InnoDB存储引擎是面向行的(row-oriented),也就是说数据的存放是按行进行存放的。
这里提到面向行(row-oriented)的数据库,那么也就是说,还存在有面向列(column-orientied)的数据库。MySQL infobright储存引擎就是按列来存放数据的,这对于数据仓库下的分析类SQL语句的执行以及数据压缩很有好处。类似的数据库还有Sybase IQ、Google Big Table。面向列的数据库是当前数据库发展的一个方向。
Page页:最多包含7992行记录
多个Row组织到一个Page页中,一个Page页即一个磁盘块大小,是io操作的最小物理存储单元,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。
每个页存放的行记录也是有硬性定义的,最多允许存放16KB/2~200行的记录,即7992行记录。
InnoDB存储引擎page页的大小为16KB,且不可以更改(也许通过更改源码可以)。
Extent区:由64个连续的页组成的
区是由64个连续的页组成的,每个页大小为16KB,即每个区的大小为1MB。
Segment 段 :最多由4个区组成
对于大的数据段,InnoDB存储引擎最多每次可以申请4个区,以此来保证数据的顺序性能。
Tablespace 表空间
表空间由三种段构成
1、叶子节点数据段:即数据段
2、非叶子节点数据段:即索引段
3、回滚段
总结:
7992行--->一页(16kB)
64个页--->一个区(1MB)
4个区---> 一个数据段(4M)
表空间由三种段构成
1.叶子节点数据段 : 即数据段
2.非叶子节点数据段 : 即索引段
3.回滚段
表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。表空间的管理模式的出现是为了数据库的存储更容易扩展,关于表空间我们还需要详细说一下
mysql 5.5版本以后出现共享表空间概念
mysql5.6版本中默认的是独立表空间
mysql5.7版本新特性共享临时表空间
2.1 共享表空间
1)概念
类似于LVM逻辑卷,是动态扩展的
默认只有12M,会根据数据的量慢慢变越来越大
优点:可以将表空间分成多个文件存放到各个磁盘上(表空间文件大小不受表大小的限制,如一个表可以分布在不同的文件上)。数据和文件放在一起方便管理。
缺点:所有的数据和索引存放到一个文件中,虽然可以把一个大文件分成多个小文件,但是多个表及索引在表空间中混合存储,这样对于一个表做了大量删除操作后表空间中将会有大量的空隙,特别是对于统计分析,日值系统这类应用最不适合用共享表空间。
2)查看共享表空间
mysql> show variables like ‘%path%‘;
+----------------------------------+------------------------+
| Variable_name | Value |
+----------------------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
| sha256_password_private_key_path | private_key.pem |
| sha256_password_public_key_path | public_key.pem |
| ssl_capath | |
| ssl_crlpath | |
+----------------------------------+------------------------+
5 rows in set (0.01 sec)
3)修改共享表空间
#1.编辑配置文件
[root@db01 ~]# vi /etc/my.cnf
# 开启独享表空间,并指定ibdata1大小为1G,ibdata2大小200M,自动扩张。
[mysqld]
# innodb_data_home_dir = /var/lib/mysql
innodb_data_file_path = ibdata1:1G;ibdata2:200M:autoextend
#配置一个就够用了,为什么要配置两个呢?
第一个共享表空间数据达到1G以后,他会往第二个表空间里面写数据,当第二个共享表空间数据也达到2M以后会动态扩容,这个文件会越来越大,就像日志一样;
这样就会导致一个问题,当越来越多的数据增加的时候,ibdata也会持续膨胀,有的达到几十G,上百G
那么,当前存储数据的磁盘分区满的时候,要怎么样去扩展数据空间呢?
#2.修改完配置文件重启
[root@db03 ~]# systemctl start mysqld # 启动会报错或者启动不了
查看日志
[root@db03 ~]# less /usr/local/mysql/data/db03.err
2021-07-21 22:26:00 50917 [ERROR] InnoDB: Data file ./ibdata1 is of a different size 768 pages (rounded down to MB) than specified in t
he .cnf file 3200 pages!
#3.报错说明共享表空间大小与当前已经存在的表空间不一致,我们要把共享表空间修改为当前共享表空间的大小才行
[root@localhost ~]# ll -h /var/lib/mysql/ibdata1
-rw-rw---- 1 mysql mysql 76M Jul 8 16:57 /var/lib/mysql/ibdata1
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
innodb_data_file_path=ibdata1:76M;ibdata2:200M:autoextend
2.2 独立表空间
对于用户自主创建的表,会采用此种模式,每个表由一个独立的表空间进行管理
优点:
缺点:
查看独立表空间
#物理查看
[root@db01 ~]# ll /application/mysql/data/world/
-rw-rw---- 1 mysql mysql 688128 Aug 14 16:23 city.ibd
#命令行查看
mysql> show variables like ‘%per_table%‘;
innodb_file_per_table=ON
innodb存储引擎支持事务,一个事务在执行时会产生大量回滚日志,即undo log
在6.1小节中我们了解到,表空间由三种段构成
1、叶子节点数据段:即数据段
2、非叶子节点数据段:即索引段
3、回滚段
也就是说回滚日志也是要存放于表空间中的,大家都是混在一起的,这会造成什么问题呢
1.1 MySQL 5.5时代的undo log(共享的undo表空间)
在MySQL5.5以及之前,大家会发现随着数据库上线时间越来越长,ibdata1文件(即InnoDB的共享表空间,或者系统表空间)会越来越大,这会造成2个比较明显的问题:
(1)磁盘剩余空间越来越小,到后期往往要加磁盘;
(2)物理备份时间越来越长,备份文件也越来越大。
这是怎么回事呢?
原因除了数据量自然增长之外,在MySQL5.5以及之前,InnoDB的撤销记录undo log也是存放在ibdata1里面的。一旦出现大事务,这个大事务所使用的undo log占用的空间就会一直在ibdata1里面存在,即使这个事务已经关闭。
那么问题来了,有办法把上面说的空闲的undo log占用的空间从ibdata1里面清理掉吗?答案是没有直接的办法,只能全库导出sql文件,然后重新初始化mysql实例,再全库导入。
1.2 MySQL 5.6时代的undo log(独立的undo表空间)
MySQL5.6中开始支持把undo log分离到独立的表空间,并放到单独的文件目录下;采用独立undo表空间,再也不用担心undo会把 ibdata1 文件搞大;也给我们部署不同IO类型的文件位置带来便利,对于并发写入型负载,我们可以把undo文件部署到单独的高速存储设备上,那么如何在独立出undo log的表空间呢?
MySQL 5.6在数据库初始化的时候使用如下三个参数就可以把undo log从ibdata1移出来单独存放
(1) innodb_undo_directory,指定单独存放undo表空间的目录,默认为.(即datadir),可以设置相对路径或者绝对路径。如果需要将undo log放到更快的设备上时,可以设置innodb_undo_directory参数,但是一般我们不这么做,因为现在SSD非常普及。
该参数实例初始化之后虽然不可直接改动,但是可以通过先停库,修改配置文件,然后移动undo表空间文件的方式去修改该参数;
(2) innodb_undo_tablespaces,指定单独存放的undo表空间个数,例如如果设置为3,即可将undo log设置到单独的undo表空间中,undo表空间为undo001、undo002、undo003,每个文件初始大小默认为10M。该参数我们推荐设置为大于等于3,原因下文将解释。该参数实例初始化之后不可改动;
(3) innodb_undo_logs,指定回滚段的个数(早期版本该参数名字是innodb_rollback_segments),默认128个,使用默认值即可。每个回滚段可同时支持1024个在线事务。这些回滚段会平均分布到各个undo表空间中。该变量可以动态调整,但是物理上的回滚段不会减少,只是会控制用到的回滚段的个数。
那么问题又来了,mysql5.6中undo log单独拆出来后就能缩小了吗?答案是不能?
mysql5.6中确实可以把undo log回滚日志分离到一个单独的表空间里,这只解决了不把ibdata1搞大的问题,至于撤销记录依然存在,空间是不能被回收(收缩)的。直到MySQL5.7 ,才支持在线收缩。
1.3 MySQL 5.7时代的undo log(共享临时表空间)
MySQL 5.7引入了新的参数,innodb_undo_log_truncate,开启后可在线收缩拆分出来的undo表空间。在满足以下2个条件下,undo表空间文件可在线收缩:
(1) innodb_undo_tablespaces>=2。因为truncate undo表空间时,该文件处于inactive状态,如果只有1个undo表空间,那么整个系统在此过程中将处于不可用状态。为了尽可能降低truncate对系统的影响,建议将该参数最少设置为3;
(2) innodb_undo_logs>=35(默认128)。因为在MySQL 5.7中,第一个undo log永远在系统表空间中,另外32个undo log分配给了临时表空间,即ibtmp1,至少还有2个undo log才能保证2个undo表空间中每个里面至少有1个undo log;
满足以上2个条件后,把 innodb_undo_log_truncate设置为ON即可开启undo表空间的自动truncate,这还跟如下2个参数有关:
(1) innodb_max_undo_log_size,undo表空间文件超过此值即标记为可收缩,默认1G,可在线修改;
(2) innodb_purge_rseg_truncate_frequency,指定purge操作被唤起多少次之后才释放rollback segments。当undo表空间里面的rollback segments被释放时,undo表空间才会被truncate。由此可见,该参数越小,undo表空间被尝试truncate的频率越高。
2.1 分离undo log表空间
可以把Undo Log从共享表空间里ibdata1拆分出去
注意,需要在安装mysql时,在my.cnf里指定,否则等创建数据库以后再指定,就会报错,如下
mysql> show variables like ‘%undo%‘;
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_undo_directory | . |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 0 |
+-------------------------+-------+
3 rows in set (0.00 sec)
mysql> set global innodb_undo_tablespaces=3;
ERROR 1238 (HY000): Variable ‘innodb_undo_tablespaces‘ is a read only variable
mysql>
已安装数据库不能修改 innodb_undo_tablespaces
我们创建新的空数据目录,重启mysql,重新初始化库,就可以把undo log从共享表空间分离出去了
# 1、创建新的数据目录并设置权限
mkdir /var/lib/mysql1
chown -R mysql.mysql /var/lib/mysql1
# 2、修改配置文件,指向新的数目录/var/lib/mysql1
vim /etc/my.cnf
[mysqld]
datadir=/var/lib/mysql1
socket=/var/lib/mysql1/mysql.sock
# 共享表空间
innodb_data_file_path=ibdata1:76M;ibdata2:12M:autoextend
# 分离
innodb_undo_logs = 128
innodb_undo_tablespaces = 4
[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
# 3、重启mysql
systemctl restart mysql
便可以看到
[root@localhost ~]# ll /var/lib/mysql1/
-rw-rw---- 1 mysql mysql 10485760 Jul 8 18:16 undo001
-rw-rw---- 1 mysql mysql 10485760 Jul 8 18:16 undo002
-rw-rw---- 1 mysql mysql 10485760 Jul 8 18:16 undo003
-rw-rw---- 1 mysql mysql 10485760 Jul 8 18:16 undo004
undo log创建好后,就不能再次修改,或者增加。
2.2 在线不停机把.ibd数据拷贝到另外一台机器上(在线迁移表)
从mysql5.6版本开始,引入了表空间传输的功能。可以把一张表从一个数据库移动到另一个数据库中或者另一台机器上。使用该功能必须满足如下条件:
注意:只有独立表空间才支持数据在线迁移!!!共享表空间不支持在线迁移!!!
假设
源主机:主机A
目标主机:主机B
# 步骤1:在主机A操作
首先为t1表加读锁(只能读,不能写,目的是保证数据一致性)(在凌晨操作--)
然后把数据从内存导出到磁盘上。
mysql> flush tables t1 for export;
# 步骤2:在主机B操作
到主机B上,创建与原表一样的表结构
create table t1(字段1 类型,字段2 类型,...);
在主机B上关闭t1表的数据空间,删除.ibd文件
mysql> alter table t1 discard tablespace;
[root@db02 db01]# ll
total 16
-rw-r----- 1 mysql mysql 65 Jul 13 15:28 db.opt
-rw-r----- 1 mysql mysql 8556 Jul 13 15:29 t1.frm #此时t1.ibd文件已删除
# 步骤3:在主机A上
将主机A上原表的t1.cfg和t1.ibd拷贝到主机B的数据目录下。
[root@db01 db01]# scp /var/lib/mysql/db01/dep.ibd root@192.168.15.52
:/var/lib/mysql/db01/t1.ibd
注意拷贝的ibd文件的属主属组与权限问题,新表授权。
[root@db02 db01]# chown -R mysql.mysql /var/lib/mysql
拷贝完后主机A执行UNLOCK TABLES;
mysql> unlock tables;
# 步骤4:在主机B上
执行ALTER TABLE t1 IMPORT TABLESPACE;就会进行恢复操作。
mysql> alter table t1 import tablespace;
Query OK, 0 rows affected, 1 warning (0.02 sec)
然后check table t1;
没问题的话,select * from t1;你会发现数据恢复了。
mysql> select * from t1;
+------+
| id |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
#注意:迁移表时,两个表的行格式必须是一样,5.7以后的行模式默认是Dynamic
mysql> show table status like "dep"; #查看dep表的行模式
设置行模式,如:create table t1(id int)row_format=Compact #5.6版本默认compact
示例:基于上述原理完成物理备份与恢复
# 1、安装mysql5.6+版
[root@localhost ~]# cat /etc/yum.repos.d/mysql.repo
[mysql56-community]
name=MySQL 5.6 Community Server
baseurl=http://repo.mysql.com/yum/mysql-5.6-community/el/7/$basearch/
enabled=1
gpgcheck=0
[root@localhost ~]# yum install mariadb-server* -y
[root@localhost ~]# yum install mariadb-* -y
[root@localhost ~]# systemctl start mysql
[root@localhost ~]# mysql -uroot
mysql> select version();
+-----------+
| version() |
+-----------+
| 5.6.51 |
+-----------+
1 row in set (0.00 sec)
mysql>
# 2、设置独立表空间
vim /etc/my.cnf
[mysqld]
default-storage-engine=innodb
innodb_file_per_table=1
# 3、准备测试数据
create database egon;
use egon;
create table t1(id int);
insert t1 values(1),(2),(3);
# 4、将/var/lib/mysql/目录下的egon库打包,并删除目录egon
cd /var/lib/mysql/
tar czf egon.tar.gz egon/
rm -rf egon/
# 5、本机测试也行,重启mysql测试,或者把数据拷贝到另外一台数据库服务器进行解压测试
[root@localhost ~]# systemctl restart mysql
[root@localhost ~]# mysql -uroot -p
mysql> create database db1;
mysql> create table test(id int); -- 表结构与源应一致
Query OK, 0 rows affected (0.01 sec)
mysql> alter table test discard tablespace; -- 删除test.ibd文件
Query OK, 0 rows affected (0.00 sec)
mysql> exit
Bye
[root@localhost ~]# tar xvf egon.tar.gz
egon/
egon/db.opt
egon/t1.frm
egon/t1.ibd
[root@localhost ~]# cp -a egon/t1.ibd /var/lib/mysql/db1/test.ibd
[root@localhost ~]#
[root@localhost ~]# mysql -uroot -p
mysql> use db1;
mysql> alter table test import tablespace;
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> select * from test;
+------+
| id |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
2.3 把ibd文件创建到其他文件夹下
在之前的版本,采用独立表空间(.ibd)存放数据时,是不能更改路径的,比如磁盘满了,恰巧没做LVM卷组,那么通过下述指令
CREATE TABLE t1(id int primary key)engine=innodb DATA DIRECTORY="/egon_data/",
就把创建t1表的.ibd放到了/data2/目录下。
3.1 MySQL 5.7的undo表空间的truncate示例
(1)安装mysql5.7
# 1、安装mysql5.7版
[root@localhost ~]# cat /etc/yum.repos.d/mysql.repo
[mysql56-community]
name=MySQL 5.7 Community Server
baseurl=http://repo.mysql.com/yum/mysql-5.7-community/el/7/$basearch/
enabled=1
gpgcheck=0
[root@localhost ~]# yum install mysql-* mysql-server* -y
(2) 首先确保如下参数被正确设置:
# 为了实验方便,我们减小该值
innodb_max_undo_log_size = 11M
innodb_undo_log_truncate = ON
innodb_undo_logs = 128
innodb_undo_tablespaces = 3
# 为了实验方便,我们增大该值
innodb_purge_rseg_truncate_frequency = 1000
(3)启动mysqld,并设置密码
[root@egon ~]# systemctl start mysqld
[root@egon ~]# grep "temporary password" /var/log/mysqld.log # 过滤出随机密码
[root@egon ~]# mysql -uroot -p‘随机密码‘
mysql> set password=password("Egon@123"); -- 弱密码会报错
(4) 创建表:
[root@egon ~]# mysql -uroot -p‘Egon@123‘
mysql> create database db1;
mysql> use db1;
mysql> create table t1(id int primary key auto_increment, name varchar(200));
Query OK, 0 rows affected (0.13 sec)
(4)插入测试数据
mysql> insert into t1(name) values(repeat(‘e‘,200));
mysql> insert into t1(name) select name from t1;
mysql> insert into t1(name) select name from t1;
mysql> insert into t1(name) select name from t1;
mysql> insert into t1(name) select name from t1;
-- 执行n次insert into t1(name) select name from t1; ,直到undo日志增大超过11M
这时undo表空间文件大小如下,可以看到有一个undo文件已经超过了11M:
-rw-r----- 1 mysql mysql 36M Jul 8 20:11 undo001
-rw-r----- 1 mysql mysql 10M Jul 8 20:11 undo002
-rw-r----- 1 mysql mysql 11M Jul 8 20:11 undo003
此时,为了,让purge线程运行,可以运行几个delete语句:
mysql> delete from t1 limit 1;
mysql> delete from t1 limit 1;
mysql> delete from t1 limit 1;
mysql> delete from t1 limit 1;
过一会之后,再查看undo文件大小,可以看到,超过101M的undo文件已经收缩到10M了。
-rw-r----- 1 mysql mysql 10M Jul 8 20:12 undo001
-rw-r----- 1 mysql mysql 10M Jul 8 20:12 undo002
-rw-r----- 1 mysql mysql 11M Jul 8 20:12 undo003
小练习:
对上述表进行一次全表更新,期间观察undo表空间一度增长到800多M,更新结束后,表看空间压缩到10M
mysql> update t1 set name="egon";
Query OK, 4194299 rows affected (2 min 51.00 sec)
Rows matched: 4194299 Changed: 4194299 Warnings: 0
原文:https://www.cnblogs.com/caodan01/p/15007637.html