前言
我们这里来看一下 MyISAM 存储引擎, 我们常见的那些 user, db, table_priv, proc 等等是基于 MyISAM
这是我们经常会提及的 两种持久化的存储引擎之一, 一是 MyISAM存储引擎, 另外一个是 InnoDB存储引擎
我们这里来看一下 MyISAM 中动态长度的数据表的相关处理
mysql.user 的表结构创建如下
CREATE TABLE `user` (`Host` char(60) COLLATE utf8_bin NOT NULL DEFAULT '',`User` char(32) COLLATE utf8_bin NOT NULL DEFAULT '',`Select_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Insert_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Update_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Delete_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Create_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Drop_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Reload_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Shutdown_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Process_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`File_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Grant_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`References_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Index_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Alter_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Show_db_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Super_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Create_tmp_table_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Lock_tables_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Execute_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Repl_slave_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Repl_client_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Create_view_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Show_view_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Create_routine_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Alter_routine_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Create_user_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Event_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Trigger_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`Create_tablespace_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`ssl_type` enum('','ANY','X509','SPECIFIED') CHARACTER SET utf8 NOT NULL DEFAULT '',`ssl_cipher` blob NOT NULL,`x509_issuer` blob NOT NULL,`x509_subject` blob NOT NULL,`max_questions` int(11) unsigned NOT NULL DEFAULT '0',`max_updates` int(11) unsigned NOT NULL DEFAULT '0',`max_connections` int(11) unsigned NOT NULL DEFAULT '0',`max_user_connections` int(11) unsigned NOT NULL DEFAULT '0',`plugin` char(64) COLLATE utf8_bin NOT NULL DEFAULT 'mysql_native_password',`authentication_string` text COLLATE utf8_bin,`password_expired` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',`password_last_changed` timestamp NULL DEFAULT NULL,`password_lifetime` smallint(5) unsigned DEFAULT NULL,`account_locked` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',PRIMARY KEY (`Host`,`User`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges'
基于 动态长度的MyISAM 的数据表的数据输出
执行 sql 如下 “select * from user;” 我们着重关注 User 为 “tz_test” 的这条用户记录
从 MYD 中读取 block_header 相关信息, 然后 mi_get_block_info 是从 block_header 中解析数据到 block_info
如果当前 block 是已经被删除掉的 block, 迭代下一个 block
然后是 上面 mi_read_cache 读取了 sizeof(block_info.header) 20 个字节的数据, 但是前面 block_header 可能仅仅只占用了 一部分字节
block_header 之后的数据, 也是数据部分, 需要将这部分数据填充到结果 buffer 中
“if (block_of_record == 0)” 的处理是在更新当前记录的 left_len 表示的是当前记录的总共长度, 以及更新输出目标缓冲
比如这里说明了当前记录 总共长度为 116, 然后 block_header 长度为 4
上面 mi_read_cache 中读取的, 还有 16 个字节属于记录的数据部分
然后就是 读取当前 block 的剩余的数据部分了, 然后内容输出到 输出缓冲区中
然后更新 left_len, 如果 left_len 表示当前记录还是有 block, 需要继续 向后遍历
动态长度的 MyISAM 这边的数据记录组织是以 block 为单位的
一个 或者 多个 block 组成一个 record
基于 动态长度的MyISAM 的数据表的数据行
数据行 的信息如下
INSERT INTO `mysql`.`user`(`Host`, `User`, `Select_priv`, `Insert_priv`, `Update_priv`, `Delete_priv`, `Create_priv`, `Drop_priv`, `Reload_priv`, `Shutdown_priv`, `Process_priv`, `File_priv`, `Grant_priv`, `References_priv`, `Index_priv`, `Alter_priv`, `Show_db_priv`, `Super_priv`, `Create_tmp_table_priv`, `Lock_tables_priv`, `Execute_priv`, `Repl_slave_priv`, `Repl_client_priv`, `Create_view_priv`, `Show_view_priv`, `Create_routine_priv`, `Alter_routine_priv`, `Create_user_priv`, `Event_priv`, `Trigger_priv`, `Create_tablespace_priv`, `ssl_type`, `ssl_cipher`, `x509_issuer`, `x509_subject`, `max_questions`, `max_updates`, `max_connections`, `max_user_connections`, `plugin`, `authentication_string`, `password_expired`, `password_last_changed`, `password_lifetime`, `account_locked`) VALUES ('%', 'tz_test', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', '', '', '', '', 0, 0, 0, 0, 'mysql_native_password', '*3F42368149F6125F466E32249394BC6D395E6D4A', 'N', '2023-08-06 05:01:16', NULL, 'N');
基于 动态长度的MyISAM 的数据表的数据录入
执行 sql 如下
create user 'tz_test_001'@'%' identified by 'tz_test_001';
grant select on tz_test_001.tz_test_02 to 'tz_test_001'@'%';
drop user 'tz_test_001';
新增 “tz_test_001” 对应的用户, 记录长度为 118
mi_find_writepos 是查询可用的节点, 如果不是必须在文件末尾增加数据, 可以吧删除的节点使用起来
否则 在末尾创建 block, 来存放当前记录
然后 就是写入 目标记录, 或者 目标记录的一部分
如果当前 block 写不完 record 的所有信息, 则进入下一个循环 mi_find_writepos + mi_write_part_record
查询可用空间方式如下, 如果是 不必须在末尾添加数据 则可以使用 删除链表的各个 block
否则 在文件末尾新建 block
写出 block 方式如下
根据各种情况封装 block_header, 这里是 tz_test_001 走的如下展开的 block 的处理
接着是写出 record 中的数据到 当前 block
将 block_header 写入 record – head_length
将当前 block 可以容纳的数据写入 record
然后将当前 block 的数据写入到 MYD 中文件对应的当前 block 的地方
基于 动态长度的MyISAM 的数据表的数据删除
遍历当前记录的 block 列表, 依次将 block 添加到 info->s->state->delink 的删除链表中
将 “block_header[0] = 0;” 表示的就是标记删除当前 block
完