mysql为啥说如果使用的是Innodb引擎的话,推荐使用varchar代替char

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql为啥说如果使用的是Innodb引擎的话,推荐使用varchar代替char相关的知识,希望对你有一定的参考价值。

参考技术A 没有说推荐使用varchar吧,varchar和char取决于字段长度是否可控,如果是可控的,定长肯定推荐使用char吧。不确定,那是推荐varchar,varchar实际的长度比设置的length要长。本回答被提问者采纳

Innodb存储引擎表空间详解

Innodb存储引擎表空间架构图

在这里插入图片描述

表空间由三种段构成

  1. 叶子节点数据段 : 即数据段
  2. 非叶子节点数据段 : 即索引段
  3. 回滚段

表空间分类

表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。表空间的管理模式的出现是为了数据库的存储更容易扩展,关于表空间我们还需要详细说一下

  • mysql 5.5版本以后出现共享表空间概念
  • mysql5.6版本中默认的是独立表空间
  • mysql5.7版本新特性共享临时表空间

共享表空间

概念
类似于LVM逻辑卷,是动态扩展的。默认只有12M,会根据数据的量慢慢变越来越大(ibdata1)。

  • 优点:可以将表空间分成多个文件存放到各个磁盘上(表空间文件大小不受表大小的限制,如一个表可以分布在不同的文件上)。数据和文件放在一起方便管理。

  • 缺点:所有的数据和索引存放到一个文件中,虽然可以把一个大文件分成多个小文件,但是多个表及索引在表空间中混合存储,这样对于一个表做了大量删除操作后表空间中将会有大量的空隙,特别是对于统计分析,日值系统这类应用最不适合用共享表空间。

查看共享表空间

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.00 sec)

修改共享表空间

# 查看ibdata1共享表空间默认的大小
[root@web01 ~]#  ll -h /var/lib/mysql/ibdata1
-rw-r----- 1 mysql mysql 12M Jul 12 18:43 /var/lib/mysql/ibdata1

# 修改mysql配置文件
[root@web01 ~]# vim /etc/my.cnf
[mysqld]
# 开启独享表空间,并指定ibdata1大小为12m,ibdata2大小200M,自动扩张。
# 注:ibdata1必须和MySQL默认的ibdata1共享表空间的大小一致,否则无法重启数据库服务
innodb_data_file_path = ibdata1:12M;ibdata2:200M:autoextend

# 重启数据库服务
[root@web01 ~]# systemctl restart mysqld

# 查看共享表空间
mysql> show variables like "%path%";
+----------------------------------+-------------------------------------+
| Variable_name                    | Value                               |
+----------------------------------+-------------------------------------+
| innodb_data_file_path            | ibdata1:12M;ibdata2:200M:autoextend |
| innodb_temp_data_file_path       | ibtmp1:12M:autoextend               |
| sha256_password_private_key_path | private_key.pem                     |
| sha256_password_public_key_path  | public_key.pem                      |
| ssl_capath                       |                                     |
| ssl_crlpath                      |                                     |
+----------------------------------+-------------------------------------+

独立表空间

概念
对于用户自主创建的表,会采用此种模式,每个表由一个独立的表空间进行管理(.ibd)。

优点:

  1. 每个表都有自已独立的表空间,易于区分与管理。
  2. 每个表的数据和索引都会存在自已的表空间中。
  3. 可以实现单表在不同的数据库中移动。
  4. 空间可以回收(除drop table操作外,表空间不能自已回收)
    4.1 Drop table操作自动回收表空间
    4.2 如果对于统计分析或是日值表,删除大量数据后可以通过:alter table tablename engine=innodb;回缩不用的空间。
    4.3 对于使innodb-plugin的Innodb使用turncate table也会使空间收缩。
    4.4 对于使用独立表空间的表,不管怎么删除,表空间的碎片不会太严重的影响性能,而且还有机会处理。

缺点:

  1. 单表增加过大,如超过100个G。

查看独立表空间

mysql> show variables like "%per_table%";
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | ON    |
+-----------------------+-------+
1 row in set (0.00 sec)

表空间(.ibd文件)数据在线迁移

从mysql5.6版本开始,引入了表空间传输的功能。可以把一张表从一个数据库移动到另一个数据库中或者另一台机器上。使用该功能必须满足如下条件:

  • Mysql版本必须是5.6及以上
  • 只支持独立表空间方式,现在版本默认开启innodb_file_per_table
  • 源库和目标库的page size必须一致,表结构必须一致
  • 如果要做表的导出操作,该表只能进行只读操作

注意:只有独立表空间才支持数据在线迁移!!!共享表空间不支持在线迁移!!!

数据库表支持的行格式:在这里插入图片描述

# 源主机:主机A
# 目标主机:主机B

# 在源主机上创建数据
mysql> create database db01;
Query OK, 1 row affected (0.00 sec)

mysql> create table t1(id int);
Query OK, 0 rows affected (0.01 sec)

mysql> insert into t1 values(1),(2),(3);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from t1;
+------+
| id   |
+------+
|    1 |
|    2 |
|    3 |
+------+
3 rows in set (0.00 sec)

# 步骤1:在主机A操作
# 首先为t1表加读锁(只能读,不能写,目的是保证数据一致性)
mysql> flush tables t1 for export;
# 查看t1表默认的行格式
mysql> show table status like "t1";
# Compact 

# 步骤2:在主机B操作
# 到主机B上,创建与原表一样的表结构
mysql> create table t1(id int)row_format=Compact;
Query OK, 0 rows affected (0.00 sec)

# 在主机B上关闭t1表的数据空间,删除.ibd文件
mysql> alter table t1 discard tablespace;

# 步骤3:在主机A上
# 我的A主机使用的源码编译安装,B主机使用的是yum安装。所以穿数据文件的路径不一样
[root@db01 ~]# scp -r /service/mysql/data/db01/t1.ibd root@192.168.15.7:/var/lib/mysql/db01/
mysql> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)

# 步骤4:在主机B上
# 修改.ibd文件的属主和属组
[root@web01 ~]# chown mysql.mysql /var/lib/mysql/db01/t1.ibd
mysql> alter table t1 import tablespace;
Query OK, 0 rows affected, 1 warning (0.02 sec)

mysql> check table t1;
+---------+-------+----------+----------+
| Table   | Op    | Msg_type | Msg_text |
+---------+-------+----------+----------+
| db01.t1 | check | status   | OK       |
+---------+-------+----------+----------+
1 row in set (0.00 sec)

mysql> select * from t1;
+------+
| id   |
+------+
|    1 |
|    2 |
|    3 |
+------+
3 rows in set (0.00 sec)

回滚日志的物理空间

innodb存储引擎支持事务,一个事务在执行时会产生大量回滚日志,即undo log。
表空间由三种段构成

1. 叶子节点数据段:即数据段
2. 非叶子节点数据段:即索引段
3. 回滚段

也就是说回滚日志也是要存放于表空间中的。

  • 共享表空间,undo log日志存放在ibdata1文件中。
  • 独立表空间,undo log日志存放在.ibd文件中。

undo log表空间

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实例,再全库导入。

MySQL 5.6时代的undo log(独立的undo表空间)

mysql5.6版本中undo log只支持单独拆分,不支持可在线收缩,可以做成lvm来扩容!!!解决undo log过度占用硬盘空间的问题

MySQL5.6中开始支持把undo log分离到独立的表空间,并放到单独的文件目录下;采用独立undo表空间,再也不用担心undo会把ibdata1文件搞大;也给我们部署不同IO类型的文件位置带来便利,对于并发写入型负载,我们可以把undo文件部署到单独的高速存储设备上,那么如何在独立出undo log的表空间呢?

MySQL 5.6在数据库初始化的时候使用如下三个参数就可以把undo log从ibdata1移出来单独存放:

innodb_undo_directory:
指定单独存放undo表空间的目录,默认为.(即datadir),可以设置相对路径或者绝对路径。
如果需要将undo log放到更快的设备上时,可以设置innodb_undo_directory参数,但是一般我们不这么做,因为现在SSD非常普及。
该参数实例初始化之后虽然不可直接改动,但是可以通过先停库,修改配置文件,然后移动undo表空间文件的方式去修改该参数;

innodb_undo_tablespaces:
指定单独存放的undo表空间个数,例如如果设置为3,即可将undo log设置到单独的undo表空间中,undo表空间为undo001、undo002、undo003,每个文件初始大小默认为10M。
该参数我们推荐设置为大于等于3,原因下文将解释。该参数实例初始化之后不可改动;

innodb_undo_logs:
指定回滚段的个数(早期版本该参数名字是innodb_rollback_segments),默认128个,使用默认值即可。
每个回滚段可同时支持1024个在线事务。这些回滚段会平均分布到各个undo表空间中。
该变量可以动态调整,但是物理上的回滚段不会减少,只是会控制用到的回滚段的个数。

那么问题又来了,mysql5.6中undo log单独拆出来后就能缩小了吗?答案是不能?

  • mysql5.6中确实可以把undo log回滚日志分离到一个单独的表空间里,这只解决了不把ibdata1搞大的问题,至于撤销记录依然存在,空间是不能被回收(收缩)的。直到MySQL5.7 ,才支持在线收缩。

MySQL 5.7时代的undo log(共享临时表空间)

MySQL5.7引入了新的参数,innodb_undo_log_truncate,开启后可在线收缩拆分出来的undo表空间。
在满足以下2个条件下,undo表空间文件可在线收缩:

innodb_undo_tablespaces>=2:
因为truncate(删除) undo表空间时,该文件处于inactive(非活动)状态,如果只有1个undo表空间,那么整个系统在此过程中将处于不可用状态。
为了尽可能降低truncate对系统的影响,建议将该参数最少设置为3;

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个参数有关:

innodb_max_undo_log_size:
undo表空间文件超过此值即标记为可收缩,默认1G,可在线修改;

innodb_purge_rseg_truncate_frequency:
指定purge操作被唤起多少次之后才释放rollback segments。
当undo表空间里面的rollback segments被释放时,undo表空间才会被truncate。
由此可见,该参数越小,undo表空间被尝试truncate的频率越高。

注意:只有MySQL 5.7版本才支持undo表空间文件可在线收缩!!!

# 修改mysql服务端配置文件,添加指定参数
vim /etc/my.cnf
[mysqld]
# 限制undo日志大小为11M
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

# 过滤出mysql进程,并结束mysql进程
ps aux | grep mysql
# root       6262  0.0  0.0 112808   964 pts/0    R+   22:12   0:00 grep --color=auto mysql
kill -9 6262

# 初始化数据库
rm -rf /var/lib/mysql/*

# 重启数据库
systemctl restart mysqld
- 测试
# 登陆数据库客户端
cat /var/log/mysqld.log | grep password
mysql -uroot -p"FVQexVe2aD"

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; 

-- 执行多次insert into t1(name) select name from t1; ,直到undo日志增大超过11M

[root@web01 ~]# ll -h /var/lib/mysql/ | grep undo*
-rw-r----- 1 mysql mysql  36M Jul 12 22:26 undo001
-rw-r----- 1 mysql mysql  20M Jul 12 22:26 undo002
-rw-r----- 1 mysql mysql  10M Jul 12 22:26 undo003

# 让purge线程运行,可以运行几个delete语句
mysql> delete from t1 limit 1; 
Query OK, 1 row affected (0.00 sec)

# 过一会之后,再查看undo文件大小,undo文件已经收缩到10M了
[root@web01 ~]# ll -h /var/lib/mysql/ | grep undo*
-rw-r----- 1 mysql mysql  10M Jul 12 22:26 undo001
-rw-r----- 1 mysql mysql  10M Jul 12 22:26 undo002
-rw-r----- 1 mysql mysql  10M Jul 12 22:26 undo003

以上是关于mysql为啥说如果使用的是Innodb引擎的话,推荐使用varchar代替char的主要内容,如果未能解决你的问题,请参考以下文章

mysql_存储引擎层-innodb buffer pool

为啥 InnoDB 为外键列添加索引

为啥MongoDB采用B树索引,而Mysql用B+树做索引

mysql中的存储引擎

mysql中InnoDB存储引擎的行锁和表锁

MySQL中innodb引擎分析(初始化)