mysql截取某段字符串_mysql截取_mysql substr截取字段

mysql 的一行记录,最终肯定是存储在磁盘上,也就是肉眼可见的文件上,今天我们的目标很简单,就是看看它到底是怎么存的。

当然还有一个更重要的目标,就是以这个为引子,带大家完全通过一手资料,来揭秘这个问题的答案。

所以大家阅读时,不要完全奔着寻找这个答案去,如果很少通过一手资料去研究问题,那么相信完整读过本文,你会有收获和启发的。

我们先准备好三样东西。

1. 准备好一个 mysql 5.7 并将其启动。

2. 准备好 mysql 的官方文档放在旁边:

3. 准备好 mysql 的源码mysql截取,万一要用呢,别怕:

一手资料,就是官方文档 +源码 +二进制文件,其中二进制文件是我们自己去磁盘中找的,一会就知道了。

Let’s Go!

mysql 会把文件存在哪里呢?先找到他。

mysql> SHOW VARIABLES LIKE 'datadir';
+---------------+---------------------------------------------+
| Variable_name | Value                                       |
+---------------+---------------------------------------------+
| datadir       | C:ProgramDataMySQLMySQL Server 5.7Data |
+---------------+---------------------------------------------+
1 row in set1 warning (0.00 sec)

我是 windows,就在这里了,进入这个目录。

这些是啥先不管,盯着它看就好了,我们继续。

第一步:创建数据库

mysql> create database flash;

盯着刚刚的文件夹看,此时会多出一个文件夹

同时这个文件夹里会多出一个文件,叫:

|-- flash

|-- db.opt

|--flash

|--performance_schema

看一眼它里面的内容,就知道他是干嘛的了。

default-character-set=latin1
default-collation=latin1_swedish_ci

default-character-set 是默认字符集,default-collation 是默认字符序。

字符集大家都了解,就不展开了。

字符序就是字符的排序和比较规则,一般以 _ci 结尾的表示大小写不敏感,_cs 结尾的表示大小写敏感,_bin 结尾的表示用编码值进行比较。

含义知道了,那我们重新设置它应该会有所变化,我们把这个数据库设置为开发时常用的 utf8mb4 格式。

ALTER SCHEMA `flash` DEFAULT CHARACTER SET utf8mb4;

再看 db.opt 文件,内容已经发生了变化。

default-character-set=utf8mb4
default-collation=utf8mb4_general_ci

OK,那我们现在对这个文件有了个初步认识,创建一个新的数据库时,首先会多出一个以数据库名为名称的文件夹,然后文件夹里面会多出一个描述数据库配置的 db.opt 文件,我们继续!

第二步:创建表

创建一张 student 表,三列,其中 id 是主键。

CREATE TABLE `flash`.`student` (
  `id` INT NOT NULL,
  `name` VARCHAR(10NOT NULL,
  `age` INT NULL,
  PRIMARY KEY (`id`)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4;

此时 flash 文件夹中,多出了两个文件

|-- flash

|--db.opt

|--student.frm

|--student.ibd

|--flash

|--performance_schema

为了严谨,我们先看下 db.opt 文件有没有变化,发现没有任何变化,说明创建表对这个 db.opt 配置信息文件,没有影响。

再点开 student.frm,坏了,乱码了。

mysql substr截取字段_mysql截取_mysql截取某段字符串

说明这个文件不是文本文件,用二进制方式打开它。

我把一些关键的地方都标上了含义,那这个文件的作用大家就一目了然了mysql截取,就是记录表结构嘛,具体的格式可以看 frm 文件结构的官方文档(写得太复杂了...我反正是没看):

db.opt 记录了数据库信息,student.frm 记录了表结构信息,那重头戏自然就在那个 student.ibd 文件了,这里一定存着具体的数据呀,索引呀等信息吧。

打开它!

果不其然还是乱码,那还是二进制打开它!截取了中间信息较为丰富的某部分。

发现一点也看不懂。

第三步:插入数据

我们加几条数据看看。

INSERT INTO `flash`.`student` (`id``name``age`VALUES ('1''dibingfa2''2');
INSERT INTO `flash`.`student` (`id``name``age`VALUES ('2''dibingfa2''2');
INSERT INTO `flash`.`student` (`id``name``age`VALUES ('3''dibingfa3''2');
INSERT INTO `flash`.`student` (`id``name``age`VALUES ('4''dibingfa4''2');
INSERT INTO `flash`.`student` (`id``name``age`VALUES ('5''dibingfa5''2');
INSERT INTO `flash`.`student` (`id``name``age`VALUES ('6''dibingfa6''2');
INSERT INTO `flash`.`student` (`id``name``age`VALUES ('7''dibingfa7''2');

再二进制打开它!

mysql截取某段字符串_mysql substr截取字段_mysql截取

发现有些东西我们可以看出点端倪了!猜测下这部分就是每一行的记录信息吧。

我们插入了七条数据,我发现这些二进制串有一段可以分割成七对,我把他单独拿出来,并且按行分割。

我们将第一行记录拆解,第一行记录的表数据是这样的。

1 dibingfa 2

在 ibd 文件中是这样的。

0800000010002480000001000000000A07A70000011B0110646962696E67666180000002

这串数据代表啥意思呢?由于本文只能参考官方文档,我们看这里,即 Innodb 行格式。

看这部分(我们的行格式是 DYNAMIC 类型,不过参考 COMPACT 类型描述也行,因为几乎一样,之后再说):

mysql substr截取字段_mysql截取某段字符串_mysql截取

不要看这么一大长串就害怕,我们一点点来,别着急。

这个文档,我可能没资格评价,但我个人觉得写的很烂,一大堆废话也没说明白格式是什么样子,每个字节表示什么。不过也可能作用并非如此。

好的官方文档应该是能把每个字节和每一位都解释清楚的,无奈这个文档不行,那我们就去找更接近一手资料的源码。

我找到了源码,还是很清晰的,注释上就写明了每一行记录的磁盘数据格式,太好了,不用看代码了。

再贴上刚刚的第一行记录。

0800000010002480000001000000000A07A70000011B0110646962696E67666180000002

来一点点看,第一部分。

|lengthofthelastnon-nullvariable-lengthfieldofdata ... ...|

...

|lengthoffirstvariable-lengthfieldofdata|

这部分是变长字段长度列表,就是依次记录所有变长字段的长度,由于我们只有一个变长字段 varchar(10) 的 name,所以就是 08,我们存储的 "dibingfa" 刚好是 8 个字节,对上了。

那如果是多个,很显然,就这样存。

mysql截取_mysql截取某段字符串_mysql substr截取字段

错!应该是这样存,也就是逆序存放,具体为啥后面说。

mysql截取_mysql substr截取字段_mysql截取某段字符串

OK,这就是第一个字节 08 所表示的含义。

再往下。

|SQL-nullflags(1bitpernullablefield),paddedtofullbytes|

第二个结构叫 NUll 值列表,用 1 位表示一个 NULL 值,要填充满一个字节,那往下的一个字节是 00,一看我们的记录中也确实没有 NULL 值,对上了。

具体来说,同样也是逆序存放的。

mysql substr截取字段_mysql截取某段字符串_mysql截取

继续。

|4bitsusedtodeletemarkarecord,andmarkapredefined

minimumrecordinalphabeticalorder|

|4bitsgivingthenumberofrecordsownedbythisrecord

(thistermisexplainedinpage0page.h)|

|13bitsgivingtheordernumberofthisrecordinthe

heapoftheindexpage|

|3bitsrecordtype:000=conventional,001=nodepointer(insideB-tree),

010=infimum,011=supremum,1xx=reserved|

|twobytesgivingarelativepointertothenextrecordinthepage|

ORIGINoftherecord

这五个字节很乱,放在一块叫记录头信息,00 00 10 00 24,其表示删除状态,记录类型,下一条记录的相对位置等。

这一大坨先放一放,因为涉及到好多额外的知识。

继续往下看。

|firstfieldofdata|

...

|lastfieldofdata|

剩下全都是具体的列数据了,从第一列到最后一列。

第一列是 ID 列,是 INT 类型的 1,占四个字节 80 00 00 01。开头的 80 是因为,正数要以 1 开头,这是 mysql 规定的,0x80 的二进制就是 1000 0000,所以这也对上了。

第二列是 name 列,是"dibingfa" 这样一个 varchar 类型的字符串。

可是与后面怎么也对应不上,这是咋回事呢?

还记不记得,mysql 每行记录会有几个隐藏列,rowid,事务 ID,回滚指针?没错,就是他们。

其中,因为有主键,所以 rowid 就不存在了,也可以说第一列要么是 mysql 为我们生成的 6 字节的 rowid,要么是用户定义的主键或其他 Unique 键,优先以用户定义的键为准。

下面我们一块看一下这五个列。(三个隐藏列,两个我们定义的列)

主键 ID:80 00 00 01

事务 ID:00 00 00 00 0A 07

回滚指针:A7 00 00 01 1B 01 10

name 列(dibingfa):64 69 62 69 6E 67 66 61

age 列(2):80 00 00 02

其中 age 列同刚刚说的一样,mysql 会为正数的前面,加一个 1,所以 age 为 2,在磁盘上存储的就是 80 00 00 02。

事务 ID 和回滚指针就涉及到事务、隔离级别和 MVCC 这一大坨八股文的知识点,这里不做展开。

行记录格式整体结构

总结下,整个一行记录的格式,叫做 mysql 的行记录格式,ROW_FORMAT。

这个 ROW_FORMAT 可以有不同的值,代表存储这一行记录的不同数据结构,其枚举记录在 remOtypes.h 文件中。

/** Innodb row types are a subset of the MySQL global enum row_type.
They are made into their own enum so that switch statements can account
for each of them. */

enum rec_format_enum {
 REC_FORMAT_REDUNDANT = 0/*!< REDUNDANT row format */
 REC_FORMAT_COMPACT = 1/*!< COMPACT row format */
 REC_FORMAT_COMPRESSED = 2/*!< COMPRESSED row format */
 REC_FORMAT_DYNAMIC = 3 /*!< DYNAMIC row format */
};

我电脑上用的是 mysql 5.7,其默认的行记录格式是DYNAMIC,这个在源码中也可以找到答案,在 ha_innodb.cc 中。

static ulong innodb_default_row_format = DEFAULT_ROW_FORMAT_DYNAMIC;

当然,可以用如下命令查询你的行格式。

show table status from flash like 'student';

所以我们今天以上讲述的格式,都是 DYNAMIC 格式的结构,总结起来如下:

记录源信息

变长字段列表

NULL 值列表

记录头信息

具体记录的各列信息

rowid 或主键(隐藏列)

事务 ID(隐藏列)

回滚指针(隐藏列)

列 1

列 2

...

列 n

刚刚那七条记录,整体分析下,就如下图。

然后多个行,一次紧密地排列,通过记录头中的下一条记录的相对位置指针信息,可以快速找到下一条记录的起始位置。

再宏观一点看,整个 ibd 文件,划分了很多个块,每个块 16 KB,我们这几行记录信息,在第四个块的某个区域内。

具体为什么是这个区域呢?因为这里是用户记录部分,前面还有文件头、页面头等信息,共占用 120 个字节,咱们今天讲的行记录部分,就从第 121 个字节开始。

慢慢的,我再和大家一起把其他部分搞清楚,那 mysql 这块我们就从最原始的磁盘数据入手,将 data 文件夹下的所有文件都搞清楚了。

最原始的数据都搞清楚了,原理还担心么?

再聊几句

其实,不要被行记录格式这种名词吓到,它只是个协议或规定罢了。

就是 mysql 规定了一种将一行记录存储在磁盘中的格式,以便于 mysql 自己的程序可以根据这个结构认识这一行记录。

所以这种协议,首先要满足让 mysql知道全部想知道的信息,比如 mysql 现在能仅仅通过 ibd 文件里的这些二进制数,知道每个字段的值都是什么吗?不能,因为它不知道表结构是什么样子,也就没法知道两个字段值之间的界限在哪里。

所以不难想到,它一定利用了 frm 文件中存储的表结构信息。

其次,要让 mysql 在知道这些信息的同时,还能更方便地利用这个结构,占用更少的存储空间,以及提升程序的便利性。

拿占用更少存储空间这块来讲,NULL 值完全可以当做普通列,也存储在后面,然后规定一个 NULL 值的二进制标识符即可。但 DYNAMIC 行记录格式规定前面放一个 NULL 值列表的结构,并且仅仅用 1 位来表示一个 NULL 值记录,这样就极大节省了空间。

再说便利性这块,上面说了变长字段长度列表和 NULL 值列表,都是逆序存储的,看似很别扭,其实就是为了程序的便利性,这里留给大家自己探索吧。

文章附赠

恭喜读到了这里,文章附赠一份小礼物,就是本文输出的一些文件。

mysql截取某段字符串_mysql截取_mysql substr截取字段

大家加我好友(公众号菜单栏中有个联系我),看朋友圈第一条,即可获得这些文件的下载路径。

哦对了,顺便提一下,记得很久之前还是用 sqlyog 去连 mysql 的,其实 mysql 下载并安装后,自带的 workbench 就很好用了。

mysql substr截取字段_mysql截取_mysql截取某段字符串

所以非常方便本机做实验。

大家方便的时候也自己玩一玩哦,我们下期见。

限时特惠:本站每日持续更新海量设计资源,一年会员只需29.9元,全站资源免费下载
站长微信:ziyuanshu688