Skip to content
On this page

MySQL 学习

查看版本

sql
select version();

-- 8.0.28
1
2
3

查看表数据硬盘情况

sql
SELECT
	TABLE_NAME '表名',
	concat( TRUNCATE ( data_length / 1024 / 1024, 0 ), ' MB' ) AS '数据',
	concat( TRUNCATE ( index_length / 1024 / 1024, 2 ), ' MB' ) AS '索引' 
FROM
	information_schema.TABLES 
WHERE
	TABLE_SCHEMA = 'laoshiren' 
GROUP BY
	TABLE_NAME 
ORDER BY
	data_length DESC;
1
2
3
4
5
6
7
8
9
10
11
12

创建表

sql
CREATE TABLE `user_operation_log`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(64) NULL DEFAULT NULL,
  `ip` varchar(20)  NULL DEFAULT NULL,
  `op_data` varchar(255)  NULL DEFAULT NULL,
  `attr1` varchar(255)  NULL DEFAULT NULL,
  `attr2` varchar(255)  NULL DEFAULT NULL,
  `attr3` varchar(255)  NULL DEFAULT NULL,
  `attr4` varchar(255)  NULL DEFAULT NULL,
  `attr5` varchar(255)  NULL DEFAULT NULL,
  `attr6` varchar(255)  NULL DEFAULT NULL,
  `attr7` varchar(255)  NULL DEFAULT NULL,
  `attr8` varchar(255)  NULL DEFAULT NULL,
  `attr9` varchar(255)  NULL DEFAULT NULL,
  `attr10` varchar(255)  NULL DEFAULT NULL,
  `attr11` varchar(255)  NULL DEFAULT NULL,
  `attr12` varchar(255)  NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

1000w 数据准备

sql
CREATE PROCEDURE batch_insert_log()
BEGIN
  DECLARE i INT DEFAULT 1;
  DECLARE userId INT DEFAULT 10000000;
 set @execSql = 'INSERT INTO `user_operation_log`(`user_id`, `ip`, `op_data`, `attr1`, `attr2`, `attr3`, `attr4`, `attr5`, `attr6`, `attr7`, `attr8`, `attr9`, `attr10`, `attr11`, `attr12`) VALUES';
 set @execData = '';
  WHILE i<=10000000 DO
   set @attr = "'测试很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的属性'";
  set @execData = concat(@execData, "(", userId + i, ", '127.0.0.1', '用户登录操作'", ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ")");
  if i % 1000 = 0
  then
     set @stmtSql = concat(@execSql, @execData,";");
    prepare stmt from @stmtSql;
    execute stmt;
    DEALLOCATE prepare stmt;
    commit;
    set @execData = "";
   else
     set @execData = concat(@execData, ",");
   end if;
  SET i=i+1;
  END WHILE;
END;;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sql

create table emp
(
    id          int comment '编号',
    workno      varchar(10) comment '工号',
    name        varchar(10) comment '姓名',
    gender      char(1) comment '性别',
    age         tinyint unsigned comment '年龄',
    idcard      char(18) comment '身份证号',
    workaddress varchar(50) comment '工作地址',
    entrydate   date comment '入职时间'
) comment '员工表';


1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql
insert into emp(id, workno, name, gender, age, idcard, workaddress, entrydate)
VALUES (1, '1', '柳岩', '', 20, '123456789012345678', '北京', '2000-01-01'),
       (2, '2', '张无忌', '', 18, '123456789012345670', '北京', '2005-09-01'),
       (3, '3', '韦一笑', '', 38, '123456789012345670', '上海', '2005-08-01'),
       (4, '4', '赵敏', '', 18, '123456789012345670', '北京', '2009-12-01'),
       (5, '5', '小昭', '', 16, '123456789012345678', '上海', '2007-07-01'),
       (6, '6', '杨逍', '', 28, '12345678901234567X', '北京', '2006-01-01'),
       (7, '7', '范瑶', '', 40, '123456789012345670', '北京', '2005-05-01'),
       (8, '8', '黛绮丝', '', 38, '123456789012345670', '天津', '2015-05-01'),
       (9, '9', '范凉凉', '', 45, '123456789012345678', '北京', '2010-04-01'),
       (10, '10', '陈友谅', '', 53, '123456789012345670', '上海', '2011-01-01'),
       (11, '11', '张士诚', '', 55, '123456789012345670', '江苏', '2015-05-01'),
       (12, '12', '常遇春', '', 32, '123456789012345670', '北京', '2004-02-01'),
       (13, '13', '张三丰', '', 88, '123456789012345678', '江苏', '2020-11-01'),
       (14, '14', '灭绝', '', 65, '123456789012345670', '西安', '2019-05-01'),
       (15, '15', '胡青牛', '', 70, '12345678901234567X', '西安', '2018-04-01'),
       (16, '16', '周芷若', '', 18, null, '北京', '2012-06-01'); 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

1.存储引擎

1.1 MySQL 体系结构

在这里插入图片描述

连接层:主要完成客户端的连接处理,授权,检查连接数。

服务层:绝大部分的核心功能都是在服务层完成,sql接口,解析器,优化器,缓存。

引擎层:可插拔式的存储引擎。索引实在存储引擎层实现的。

存储层:日志,数据,索引等。

1.2 存储引擎简介

什么是存储引擎?

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也被称作表类型。

查询建表语句

sql
show create table user_operation_log;

CREATE TABLE `user_operation_log` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` varchar(64) DEFAULT NULL,
  `ip` varchar(20) DEFAULT NULL,
  `op_data` varchar(255) DEFAULT NULL,
  `attr1` varchar(255) DEFAULT NULL,
  `attr2` varchar(255) DEFAULT NULL,
  `attr3` varchar(255) DEFAULT NULL,
  `attr4` varchar(255) DEFAULT NULL,
  `attr5` varchar(255) DEFAULT NULL,
  `attr6` varchar(255) DEFAULT NULL,
  `attr7` varchar(255) DEFAULT NULL,
  `attr8` varchar(255) DEFAULT NULL,
  `attr9` varchar(255) DEFAULT NULL,
  `attr10` varchar(255) DEFAULT NULL,
  `attr11` varchar(255) DEFAULT NULL,
  `attr12` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2184001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

建表语句没有指定引擎,使用默认的存储引擎InnoDB(从MySQL5.5之后就是InnoDB)

查看当前数据库支持的存储引擎

sql
show engines;
1

在这里插入图片描述

指定存储引擎 MyISAM

sql
create table my_myisam(
id int,
    name varchar(10)
) engine = MyISAM;
1
2
3
4

1.3 存储引擎特点

InnoDB

介绍

InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在MySQL 5.5 之后,InnoDB 是默认的MySQL 引擎。

特点

DML 操作遵循 ACID 模型,支持事务

行级锁,提高并发访问性能。

支持 外键 FOREIGN KEY 约束,保证数据的完整性和正确性。

文件

xxx.ibd:xxx表示表名,InnoDB 引擎的每张表都会对应一个这样的表空间文件,存储该表的表结构(frm(8.0之后表结构存储到了sdi)、sdi)、数据和索引。

参数:innodb_file_per_table 8.0版本这个参数是打开的,每一张表有自己的表空间

在这里插入图片描述

在这里插入图片描述

在InnoDB 逻辑存储结构当中,Page 是磁盘操作的最小单元,一个Extent的大小是固定的(1M),一个Page (16k)

MyISAM

介绍

MyISAM是MySQL早期的默认存储引擎

特点

不支持事物,不支持外键

只是表锁,不支持行锁

访问速度快

文件

xxx.sdi: 存储表结构信息

xxx.MYD: 存储表数据

xxx.MYI: 存储索引

在这里插入图片描述

Memory

介绍

Memory 引擎的表数据是存放在内存中的,由于受到硬件问题或断点问题的影响,只能将这些表作为临时表或者缓存使用。

特点

内存存放

hash索引(默认)

文件

xxx.sdi: 存储表结构信息

特点InnoDBMyISAMMemory
存储限制64TB
事务安全支持--
锁机制行锁表锁表锁
B+Tree索引支持支持支持
Hash索引--支持
全文索引支持(5.6版本之后)支持-
空间使用N/A
内存使用中等
批量插入速度
支持外键支持--

1.4 存储引擎选择

在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以更具实际情况选择多种引擎进行组合。

InnoDB :是MySQL 的默认引擎,支持事务外键,对事务完整性要求比较高,要求一定并发条件下数据一致性,除查询和插入外还包括更新删除操作,InnoDB 比较适合

MyISAM :以读操作插入操作为主少有更新和删除操作,并对事务的完整性,并发要求不是很高,MyISAM 比较适合

Memory 访问速度快,对于大数据表无法缓存在内存中,而且无法保证数据安全性。

在实际业务开发过程中MyISAM 被MongoDB 取代了,Memory 被Redis 取代了。

2 索引

2.1 索引概述

索引(index)是帮助MySQL 高效获取数据的有序数据结构。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

在这里插入图片描述

优缺点:

优点缺点
提高数据库检索的效率,降低数据库的IO成本索引列也是需要占用空间的
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。索引大大提高了查询效率,同时也降低更新表的速度,如果对表进行INSERT,UPDATE,DELETE时,效率降低

2.2 索引结构

MySQL 的索引实在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:

索引结构描述
B+Tree索引最常见的索引类型,大部分引擎都支持B+树索引
Hash索引底层数据结构是hash表实现的,只有精确匹配索引列的查询才有效,不支持范围查询
R-Tree(空间索引)空间索引是MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-Text(全文索引)是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene,Solr,ES

支持情况

索引InnoDBMyISAMMemory
B+Tree 索引支持支持支持
Hash 索引不支持不支持支持
R-Tree 索引不支持支持不支持
Full-text5.6版本之后支持支持不支持

平时所说的索引,没有特别指明,都是B+Tree结构的索引。

BTree

二叉树

在这里插入图片描述

二叉树的缺点:顺序插入式,会形成一个链表,查询性能大大降低。大数量情况下,层级较深,索引速度慢。

红黑树

在这里插入图片描述

红黑树的缺点:大数量情况下,层级较深,检索速度慢。

B-Tree(多路平衡查找树)

以一颗最大度数(max-degree)为5的B-Tree为例(每个节点最多寸4个Key,5个指针)

树的度数指的是一个节点的子节点个数

在这里插入图片描述

Data Structure Visualizations 链接: https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

插入满Max-degree的节点会进行分裂,中间元素向上分裂

B+Tree

特点:

  • 所有的元素都在叶子节点,非叶子节点只起到了索引的作用

  • 在B+Tree数据结构中叶子节点,会形成一个单项链表

以一颗最大度数(max-degree)为4的B+Tree为例

在这里插入图片描述

插入满Max-degree的节点会进行分裂,中间元素向上分裂,在叶子节点也会存放,而且会形成一个单项列表。

在MySQL索引数据结构对经典B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能。

在这里插入图片描述

Hash

哈希索引就是采用一定的hash 算法,将键值换算成新的hash 值,映射到对应的槽位上,然后存储在hash 表中。

如果两个(或多个)键值,映射到一个相同槽位上,他们就产生了hash 冲突(也称为hash 碰撞),可以通过链表来解决。

在这里插入图片描述

特点:

  • hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,......)
  • 无法利用索引完成排序操作
  • 查询效率高,通常只要一次索引就可以了,效率通常要高于B+Tree 索引

在MySQL 中,支持hash 索引的事Memory 引擎,而在InnoDB 中具有自适应hash 功能,hash索引是存储引擎根据B+Tree 索引在指定条件下自动构建的。

为什么InnoDB 存储引擎选择B+Tree 索引结构?

相对于二叉树,层级更少,搜索效率高

对于B-Tree ,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页存储的键值减少,指针耕者减少,同样保存大量数据,只能增加树的高度,导致性能降低

对于Hash 索引 B+Tree 支持范围匹配及排序操作

2.3 索引分类

分类含义特点关键字
主键索引针对于表中主键的索引默认自动创建,只能有一个PRIMARY
唯一索引避免同一个表中某数据列中的值重复可以有多个UNIQUE
常规索引可以定位特定数据可以有多个
全文索引全文索引查找的是文本中的关键字,而不是比较索引中的值可以有多个FULLTEXT

在InnoDB存储引擎中,根据索引的形式,又可以分为以下两种:

分类含义特点
聚集索引(Clustered Index)将数据存储与索引放到了一块,索引结构的子节点保存了行数据必须有,而且只有一个
二级索引(Secondary Index)将数据与索引分开存储,索引结构的子节点关联的是对应的主键可以存在多个

聚集索引选取规则:

如果存在主键,主键索引就是聚集索引。

如果不存在主键,将使用第一个唯一索引(UNIQUE)作为聚集索引。

如果没有主键,或者没有合适的唯一索引,则InnoDB 会自动生成一个rowid 作为隐藏的聚集索引。

在这里插入图片描述

select * from user where name = 'Arm';

首先走二级索引,查找到Arm,然后拿到主键索引,然后通过聚集索引拿到行数据,这个操作也叫回表。

InnoDB指针占用6个字节控件,主键为bigint,占用字节数为8

2.4 索引语法

创建索引

sql
CREATE [UNIQUE|FULLTEXT] INDEX index_name ON table_name (col0,col1,...);
1

查看索引

sql
SHOW INDEX FROM table_name;
1

删除索引

sql
DROP INDEX index_name ON table_name;
1

2.5 SQL 性能分析

查看执行频次

MySQL 客户端连接成功后,通过show [session|global] status 命令可以提供服务器状态信息,通过如下指令可以查看数据的INSERT,UPDATE,DELETE,SELECT 的访问频次

sql
SHOW GLOBAL STATUS LIKE 'Com_______';
1

在这里插入图片描述

慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL 语句的日志。

MySQL 的慢查询日志默认没有开启,需要在MySQL 的配置文件(/etc/my.cnf)中配置如下信息:

# 开启慢查询日志开关
slow_query_log=1
# 设置慢查询日志时间为2秒
long_query_time=2
1
2
3
4

配置完毕之后,重启MySQL 服务器,查看慢日志文件中的记录信息 /var/lib/mysql/localhost-slow.log

shell
# Time 2022-03-05T15:45:39.688679Z
# User@Host: root[root] @ locahost [] Id: 8
# Query_time: 13.350650 Lock_time 0.000358 Rows_sent: 1 Rows_examined: 0

use laoshiren;
SET timestamp=1635435926;
select count(1) from user_operation_log;
1
2
3
4
5
6
7

show profiles

profile详情

show profile能够在做SQL优化是帮助我们了解时间都消耗到哪里去了,通过have_profiling 参数,能够看到当前MySQL 是否支持profile操作

sql
select @@have_profiling;
1

在这里插入图片描述

默认profiling 是关闭的 (select @@profiling),可以通过set 语句在session/global 级别开启profiling:

sql
SET profiling = 1;
1

执行一系列的业务SQL的操作,然后通过如下指令查看SQL 的执行耗时

sql
-- 查看每一条SQL的耗时情况
show profiles;

-- 查看指定Query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;

-- 查看query_id的SQL cpu使用情况
show profile cpu for query query_id;
1
2
3
4
5
6
7
8

执行了3条SQL

sql
SELECT * from user_operation_log where id = '456465';

select * from  user_operation_log where user_id = '15646';

select * from user_operation_log LIMIT 10000, 10;
1
2
3
4
5

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

explain

explain 执行计划

EXPLAIN 或者 DESC 命令获取MySQL 如何语句的信息,包括在SELECT 语句执行过程中表如何连接和连接顺序

sql
-- 在SQL语句前加上关键字 EXPLAIN/DESC
EXPLAIN SELECT col0,clo1,... from table_name WHERE condition;
1
2

在这里插入图片描述

含义
idselect 查询的序号,表示查询中的执行select 子句或者操作表的顺序(id相同,顺序从上到下,id 不同值越大越先执行)
select_type表示 SELECT 的类型,常见的取值有 SIMPLE (简单表,不使用表连接和子查询),PRIMIAY(主查询,即外层的查询),UNION ,SUBQUERY (select/where 之后包含了子查询)等
table
partitions
type表示连接的类型,性能由好到差的连接类型为 NULL,system,const,eq_ref,ref,range,index,all
possible_keys可能用到的索引
key实际用到的索引
key_len表示索引使用的字节数
ref
rows查询预估值
filtered返回结果占读取行数的百分比,越大越好
Extra额外信息

2.6 索引使用

验证索引效率

未建立索引的列user_id作为条件查询时,耗时143s。

sql
select * from user_operation_log where user_id = '10000010';
1

在这里插入图片描述

当存在索引时查询耗时0.26s

sql
select * from user_operation_log where id = 13
1

对于字段创建索引

sql
create index index_user_id on user_operation_log(user_id);
1

创建索引就相当于去构建B+Tree的数据结构,需要一定耗时。

在这里插入图片描述

再次查询发现只要0.419s在这里插入图片描述

在这里插入图片描述

最左前缀法则

如果索引了多列(联合索引),需要村收最左前缀法则,最左前缀发法则指的是查询从索引的最左列开始,并不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

在这里插入图片描述

首先对这3个字段创建索引,来测试最左前缀发展。

sql
explain 
select * from emp where 1=1
and name = '韦一笑' 
and gender = '' 
and  workaddress = '上海'
1
2
3
4
5

当按照顺序且不跳过字段查询是可以使用索引,即使是少了几个字段也是可以使用的(缺少后面字段)。

当跳过第一个字段(根据genderworkaddress)查询时,发现是无法使用索引的

在这里插入图片描述

前文说了,跳跃了某一列,部分索引失效,观察索引长度,即只使用了跳跃列前面的索引长度。

sql
select * from emp where 1=1
and gender = '' 
and workaddress = '上海'
and name = '韦一笑' 
1
2
3
4

当查询为如上sql时也会走索引

范围查询

在联合索引中,出现范围查询(><),范围查询右侧的列索引失效。

在这里插入图片描述

当不得不出现范围查询,且业务允许的时候,大于可以替换为大于等于。

索引失效

索引列运算

不要再索引列上进行运算操作,索引将会失效。

在这里插入图片描述

字符串不加引号

字符串类型的字段使用时,不加引号,索引将失效

在这里插入图片描述

模糊查询

如果仅仅是尾部模糊匹配,索引不会失效,如果是头部匹配,索引失效。

or链接的条件

or 分割开的条件,如果or 前的条件有所用而后面列没有索引,那么涉及的索引都不会被用到。

在这里插入图片描述

数据分布影响

如果MySQL 评估使用索引比全表更慢,则不使用索引。

在这里插入图片描述

在这里插入图片描述

is nullis not null 是否使用索引取决于MySQL 表的数据分布情况。

SQL 提示

SQL提示是优化数据库的一个重要手段,简单来说,在SQL语句种加入一些人为的提示来达到优化操作的目的。比如: 一个联合索引(name,id_card,status)和普通索引(name)到底用哪个索引呢?

use index
sql
-- 使用某个索引
explain select * from emp use index(idx_name_gen_add) where name = '韦一笑'
1
2
ignore index
sql
-- 忽略某个索引
explain select * from emp ignore index(idx_name_gen_add) where name = '韦一笑'
1
2
force index
sql
-- 强制使用某个索引
explain select * from emp force index(idx_name_gen_add) where name = '韦一笑'
1
2

覆盖索引&回表查询

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引已经全部能够找到),减少select *

Explain SQL时关注Extra 列出现using index condition或者NULL 说明查找使用了索引,但是需要回表查询数据。

using where;using index说明查找使用了索引,但是需要的数据都在索引列种能够找到,所以不需要回表查询。

当查询的字段不在索引种能够找到,从而查询聚集索引拿到数据,这样的操作称作回表。

在这里插入图片描述

前缀索引

表结构当中经常会出现文本类型的字段,当字段类型为字符串(varchar,text)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时浪费大量的磁盘IO,影响查询效率,此时可以将字符串的一部分前缀建立索引,这样可以大大节约索引空间,从而提高索引效率。

sql
create index idx_xxx on table (column(N));
1

前缀长度

可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和表记录的总数的比值,索引选择性越高,查询效率越高。唯一索引的选择性是1,这是做好的索引选择性,性能也是最好的。

sql
-- 查询截取前N个字符的不重复的记录 / 所有记录树 为该字段的选择性
SELECT count(DISTINCT(substring(NAME, 1, N ))) / count(*) FROM TABLE
1
2

在这里插入图片描述

单列&联合索引

单列索引:即一个索引只包括一个字段。

联合索引:即一个索引包括多个字段。

在业务场景种,如果存在多个查询条件,考虑针对于查询字段建立索引是,建议建立联合索引,而非单列索引。多条件联合查询,MySQL 优化器会评估那个字段的所有效率更好,会选择该索引完成本次查询。

2.7 设计原则

  1. 针对于数据量比较大,查询比较频繁的表
  2. 针对于常作为查询条件,排序,分组操作的字段建立索引。
  3. 尽量选择分区度高的列作为索引,尽量做唯一索引,区分度越高,效率越高。
  4. 如果是字符串且字符串长度较长,可以针对字段的特点,建立前缀索引。
  5. 尽量使用联合索引,减少单列索引,查询时联合索引喝多时候可以覆盖索引,节省存储空间避免回表。
  6. 索引越多,维护表结构的代价越大,影响增删改的效率。
  7. 如果所有列不能为空时,创建表时使用NOT NULL约束,当优化器知道每列包含NULL 值时,可以更好的确定哪个索引最有效地用于查询。

3 SQL优化

3.1 插入数据

批量操作

2-1000条

sql
insert into emp (1, '1', '柳岩', '', 20, '123456789012345678', '北京', '2000-01-01'),
       (2, '2', '张无忌', '', 18, '123456789012345670', '北京', '2005-09-01');
1
2

手动提交事务

sql
start transaction;
insert ....
commit;
1
2
3

如果一次性涉及大批量数据,insert 语句性能较低,此时可以使用MySQL 的load 指令进行插入

shell
# 客户端连接服务器时,加上参数,--local-infile
mysql --local-infile -uroot -p

# 设置全局参数local-infile 为1 开启从本地加载文件导入数据的开关
set global local_infile = 1;

# 执行load 指令将准备好的数据,加载到表结构当中
load data local file '/home/xxx.log' into table 'xxx' fields terminated by ',' lines terminated '\n';
1
2
3
4
5
6
7
8

3.2 主键优化

在InnoDB 存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(Index Organized Table IOT)。

在这里插入图片描述

一个Page 默认是16K ,一个Extent 是固定的,是1M。一个Page 是InnoDB 磁盘管理的最小单元。

页分裂

页可以为空,也可以填充一半,也可以填充满。每个页包含了2-N行数据(如果一行数据过大,会行溢出),根据主键排序。

在这里插入图片描述

在这里插入图片描述

首先会新获取一个page, 将page 页的50%之后的数据,迁移到新开辟的页面,就新数据50插入到新页面,然后重新设置页的表指针。这个操作成为页分裂。

页合并

当删除一行记录时,实际上记录并没有被物理删除,而是被标记(flaged)为删除并且它的空间变得被其他记录声明使用。当页中删除的记录达到MERGE_THREADSHOLD(默认页的50%),InnoDB 会开始寻找最靠近的页看看是否可以将2个页合并优化空间使用。

在这里插入图片描述

MERGE_THREADSHOLD:合并页的阈值,可以自己设置,在创建表或者创建索引时指定。

主键设计原则

  • 满足业务需求的情况下,尽量降低主键的长度(二级索引存储聚集索引的值)。
  • 插入数据时,尽量选择顺序插入,选择AUTO_INCREMENT 自增主键。
  • 尽量不要使用UUID 做主键或者是其他自然主键。
  • 业务操作时,避免对主键的修改。

3.3 order by 优化

  • using filesort : 通过表的索引或者扫描权标,读取满足条件的数据行,然后在排序缓冲区sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都是 FileSort 排序。
  • using index : 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。

emp中的idcard索引删除。

sql
-- 删除索引
DROP INDEX idx_id_card ON emp;

EXPLAIN select  *  FROM `emp` order by idcard;


explain select id, workno, age,idcard  from emp order by workno desc, age desc ,idcard desc;

1
2
3
4
5
6
7
8

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

然而根据最左原则,排序字段应该也要跟着索引指定的字段顺序进行排序

在这里插入图片描述

当select 字段不出现在索引中,需要回表操作,此时,缓冲区会用filesort 进行排序。

小结

  • 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
  • 尽量使用覆盖索引。
  • 多字段排序,一个升序,一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC )。
  • 如果不可避免的出现filesort,大数据量排序,可适当增大排序缓冲区大小 sort_buffer_size(默认256k)。

3.4 group by 优化

先删除表上其他索引

sql
drop index xxx on TABLE;

explain select workaddress,count(1) from emp group by  workaddress
1
2
3

在这里插入图片描述

Using temporary使用临时表性能非常低。

sql
-- 创建一个联合索引
create index idx_add_gender on emp(workaddress,gender);

explain select workaddress,count(1) from emp group by  workaddress
1
2
3
4

在这里插入图片描述

当group by 不满足最左前缀法则,则用不到索引,先会使用临时表。

在这里插入图片描述

当查询涉及如上的查询,在查询条件添加where 条件且刚好满足最左原则则查询可以使用索引。

在这里插入图片描述

分组操作时,索引的适合用也是尽可能满足最左前缀法则。

3.5 limit 优化

user_operation_log这张表有1000w的数据。

sql
select * from user_operation_log limit 1000000,10
1

在这里插入图片描述

时长显然是不可接受的。在对于limit 来说,大数据量的情况下,越往后效率越低,耗时越长。MySQL需要排序前N 条记录,仅仅返回N-(N+n)条记录,其他记录设计,查询排序的代价非常大。

官方推荐使用覆盖索引加子查询的方式。

在这里插入图片描述

MySQL 不支持此类语法 select * from user_operation_log where id in (select id from user_operation_log order by id limit 1000000,10),8.0的版本不支持在 in 条件的子查询里使用limit,所以要修改写法。

sql
select * from user_operation_log u,(select id from user_operation_log order by id limit 1000000,10) a where u.id = a.id
1

在这里插入图片描述

在这里插入图片描述

3.6 count 优化

MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高。而在InnoDB 引擎就麻烦了,他执行count(*)的时候,需要把数据一行一行地从引擎里读,然后累积计数。

count

count()是一个聚合函数,对于返回的结果集,一行行地判断,如果count() 函数的参数不是NULL 就累加1,否则不加,最后返回累计值。

InnoDB 在count(主键)时,会遍历整张表,把每一行的主键ID的值都取出来,返回给服务层。服务层拿到主键后,直接进行累加(主键不可能为null)。

count(1),遍历整张表,不取值,对于返回的每一层,放一个数字1,进行累加。

count(*),InnoDB引擎不会把全部字段取出来,而是专门做了优化,直接按行进行累加。

按照效率 count(字段)< count(pk)< count(1)≈count(*)

3.7 update 优化

update 时候尽量使用ID作为条件更新,不然update 会因为没有索引,从而将行锁升级为表锁。InnoDB 的行锁只针对索引加的锁,不是针对记录加的锁,并且索引不能失效,否则会从行锁升级为表锁。

4 锁

4.1 概述

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库种,除传统的计算机资源(CPU,RAM,I/O)的争用以外,数据也是一种提供多用户共享的资源。如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更复杂。

按照锁的粒度来分,MySQL中的锁分为以下三类

  • 全局锁:锁定数据库中的所有表。
  • 表级锁:每次操作锁住整张表
  • 行级锁:每次操作锁住对于的行数据。

4.2 全局锁

对整个数据库实例枷锁,枷锁后整个实例就处于制度状态,后续的DML的写语句,DDL语句,以及更新操作的事务提交语句都将被阻塞。通常用于备份数据mysqldump

4.3 表级锁

表级锁,每次操作锁住整张表。锁粒度打,发视锁冲突的概率高,并发度最低,应用在MyISAM,InnoDB等存储引擎中。

对于表级锁,主要分三类

  1. 表锁
  2. 元数据锁
  3. 意向锁

表锁

表锁分为2类

  1. 表共享读锁(read lock)
  2. 表独占写锁(write lock)

加锁:

sql
lock tables TABLE read/write
1

释放锁:

sql
unlock tables 
1
读锁

在这里插入图片描述

客户端1在表上加锁,客户端1和其他客户端都只能够查询,不能够写。当客户端1去写入数据会直接提示 Table 'xxx' was locked with a READ lock and can't be updated,当其他客户端写入数据时,会直接阻塞,直到客户端1解锁。

在这里插入图片描述

写锁

在这里插入图片描述

客户端1在表上加锁,只有客户端1能够读写。当其他客户端读写数据时,都会直接阻塞,直到客户端1解锁。

在这里插入图片描述

读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。

元数据锁

元数据锁(meta data lock ), MDL 加锁过程是系统自动控制,无需显示使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML和DDL冲突,保证读写的正确性

在MySQL 5.5 中引用了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当表结构进行变更操作的时候,加上MDL写锁(排他)。

对应SQL锁类型说明
lock tables xxx read/writeSHARED_READ_ONLY/SHARED_NO_READ_WRITE
select ,select... lock in share modeSHARED_READ与SHARED_READ,SHARED_WRITE兼容。与EXCLUSIVE互斥
insert ,update, delete, select... for updateSHARED_WRITE与SHARED_READ,SHARED_WRITE兼容。与EXCLUSIVE互斥
alter table...EXCLUSIVE与其他的MDL都互斥
sql
select object_type,object_schema,object_name,lock_type,lock_duration from performace_schema.metadata_locks;
1

当开启一个事务的时候不会产生元数据锁,只有增删查改,修改表结构的时候才会看到自动加锁。

意向锁

为了避免DML 在执行时,加的行锁与表锁的冲突,在InnoDB 中应如何了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。

在这里插入图片描述

不用逐行检查行锁,而是检查意向锁的情况,如果意向锁和当前加的锁是兼容的,如果兼容直接加锁,如果不兼容就会处于阻塞状态,知道A线程提交事务,解锁意向锁。

在这里插入图片描述

意向共享锁(IS)意向拍打锁(IX)
由语句select ... lock in share mode添加。由 insert,update,delete,select ... for update添加。
与表锁共享锁read 兼容,与表锁排他write锁互斥与表锁共享锁read ,表锁排他write锁互斥。意向锁之间不会互斥

通过以下SQL 查看意向所以及行锁的加锁情况。

sql
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
1

意向锁主要解决的问题是在InnoDB 引擎在加表锁和行锁的冲突问题。

4.4 行级锁

行级锁,每次操作锁住对应的行数据。锁的力度小,发生锁冲突的概率低,并发度最高,应用在InnoDB 存储引擎中。

InnoDB 的数据是基于索引组织的。行锁是通过对表锁上的索引项加锁实现的,而不是对记录加的锁。对于行级锁,主要分一下三类:

  • 行锁(Record Lock):锁定单个记录的锁,防止其他事务对此进行update 和 delete。在RC、RR 隔离级别下都支持。
  • 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引间隙不变,防止其他事务在这个间隙进行insert ,产生幻读。在RR 隔离级别下都支持。
  • 临间锁(Next-key Lock):行锁和间隙锁的组合,同时锁住数据,并锁住数据前面的间隙Gap 。在RR 隔离级别下支持。
  • 在这里插入图片描述

行锁

InnoDB 实现了一下两种类型的行锁:

  • 共享锁(S):允许一个事务读取一行,阻止其他事物获得相同数据集的排他锁。
  • 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事物相同数据集的共享锁和排他锁。
S 共享锁X 排他锁
S 共享锁兼容冲突
X 排他锁冲突冲突
SQL行锁类型说明
INSERT...排他自动加锁
UPDATE...排他自动加锁
DELETE...排他自动加锁
SELECT不加锁
SELECT LOCK IN SHARE MODE共享需要手动加LOCK IN SHARE MODE
SELECT FOR UPDATE排他需要手动加 FOR UPDATE

默认情况下,InnoDB 在RR 事物隔离级运行 InnoDB 使用 Next-key Lock 锁进行搜索和索引扫描防止幻读。

  1. 针对唯一所有检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
  2. InnoDB 的行锁是针对于索引加的锁,不通过索引检索数据,那么InnoDB 将对表中所有的记录加锁,此时就会升级为表锁。

间隙锁/临键锁

  • 索引上的等值查询(唯一索引),给不存在的记录加锁,优化为间隙锁。
  • 索引上的等值查询(普通查询),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。
  • 索引上的范围查询(唯一索引)会访问到不满足条件的第一个值位置。

注意:间隙锁唯一的目的是防止其他事物插入间隙,间隙锁可以共存,一个事务采用的间隙所不会阻塞另一个间隙上采用的间隙锁。

5 InnoDB 引擎

InnoDB 是MySQL5.5 版本之后的默认的存储引擎。

5.1 逻辑存储结构

在这里插入图片描述

表空间(ibd文件),一个MySQL 实例可以对应多个表空间,用于存储记录、索引等数据。

段(Segment),分为数据段(Leaf node segment)、索引段(Non-leaf node segment)、回滚段(Rollbak segment)。InnoDB 是索引组织表,数据段就是B+tree的叶子节点,索引段即为B+Tree的非叶子节点。段用来管理多个Extent。

区(Extent)表空间的单元结构,每个区的大小为1M,默认情况下InnoDB 存储引擎页大小为16k,即一个区中一共有64个连续的页。

页(Page),是InnoDB 存储引擎磁盘管理的最小单元,每个页默认大小为16k。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请4-5个区。

行(Row),InnoDB 存储引擎数据是按行存放的。

Trx_id: 每次对某条记录进行改动时,都会把对应的事务id 赋值给trx_id 隐藏列

Rool_pointer:每次对某条记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列相当于一个指针,可以通过它来找到该记录的修改前的信息。

5.2 架构

MySQL5.5 版本开始,默认使用InnoDB 存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛,下面是InnoDB 架构图,左侧为内存结构,右侧为磁盘结构。

在这里插入图片描述(https://img-blog.csdnimg.cn/7cb0c00ecaca4043ac877bce74192f83.png

内存结构

在这里插入图片描述

Buffer Pool

缓冲池是主内存中的一个区域,里面可以缓存磁盘上进场操作的真实数据,在执行增删改查操作时,先操作缓冲池的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。

缓冲池有一个一个的块,叫做缓冲池。缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态Page 被分为3类:

  • free page:空闲page,未被使用。
  • clean page:被使用page,数据没有被修改过。
  • dirty page:脏页,被使用page,数据被修改过,数据与磁盘的数据产生了不一致
Change Buffer

更改缓冲区(针对于非一二级所以页),在执行DML 语句时,如果这些数据Page 没有在Buffer Pool 中,不会直接操作磁盘,而会将数据变更存在缓冲区Change Buffer 中,在未来数据被读取时,再将数据合并恢复到Buffer Pool 中,再讲合并后的数据刷新到磁盘中。

在这里插入图片描述

Change Buffer的意义

与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响数中不相邻的二级索引页。如果每一次都操作磁盘,会造成大量磁盘IO,有了Change Buffer 之后,我们可以在缓冲池进行合并处理,减少磁盘IO。

Adaptive Hash Index

hash 索引最大优势在于快,因为他只需要一次匹配就可以完成(前提是不存在hash冲突的情况下),B+Tree 往往需要2-3次。但是他的弊端是不能够支持范围查询,只能做等值匹配。所以InnoDB 引擎就做了这个自适应hash 。

自适应hash 索引,用于优化对Buffer Pool 数据查询。InnoDB 存储引擎会监控对表上各索引的查询,如果观察到hash 索引可以提高速度,则建立hash 索引,称之为自适应hash 索引。

自适应hash 索引,无须人工干预,是系统根据情况自动完成。

参数:adaptive_hash_index

Log Buffer

日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log,undo log),默认大小是16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入删除许多行的事务,增加日志缓冲区的大小可以节省磁盘I/O。

参数:innodb_log_buffer_size(缓冲区大小),innodb_flush_log_at_trx_commit(日志刷新到磁盘的时机)

刷新时机默认是1,1 日志在每次事务提交时写入并刷新到磁盘,0 每秒将日志写入并刷新到磁盘一次, 2 日志在每次事务提交后写入并每秒刷新到磁盘一次。

磁盘结构

在这里插入图片描述

System Tablespace

系统表空间是更新缓冲区存储的区域。如果表示在系统表空间而不是每个表文件或者通用表空间中创建的,它也可能包含表和索引的数据(在MySQL5.x版本中还包含InnoDB数据字典、undolog 等)

参数:innodb_data_file_path

File-Per-Table Tablespaces

每个表的文件表空间包含单个InnoDB 表的数据和索引,并存储在文件系统上的单个数据文件中。

参数:innodb_file_per_table

sql
-- 创建表空间
create tablespace xxx add datafile 'xxx.ibd' engine = innodb;
1
2
General Tablespaces

通用表空间,需要通过Create tablespace语法创建通用表空间,在创建表时,可以指定该表空间。

sql
CREATE TABLE xxx(...) TABLESPACE xxxx;
1
undo Tablespaces

撤销表空间,MySQL 实例在初始化时会自动创建2个默认的undo表空间(初始大小为16M),用于存储undo log日志。

Temporary Tablespaces

InnoDB 使用会话临时表空间和全局临时表空间。存储用户会话和零时表等等数据。

Doublewrite Buffer Files

双写缓冲区,innoDB 引擎将数据页从Buffer Pool 刷新到磁盘前,先将数据写入到双写缓冲区文件中,便于系统异常时恢复数据。

Redo Log

Redo Log 是用来实现事务的持久性。该日志文件由两部分组成。重做日志缓冲(redo log buffer)已经重做日志文件(redo log),前者是在内存中,后者在磁盘中。事务提交之后会把所有修改信息都会存到该日志中,用于在刷新脏页到磁盘时,发送错误时,进行数据恢复使用。以循环方式写入重做日志文件。

后台线程

在这里插入图片描述

后台线程的作用就是将内存里缓冲池的数据在合适的时机刷新到磁盘文件当中。

Master Thread

核心后台线程,负责调度其他线程,还负责将缓冲池的数据异步刷新到磁盘中,保持数据一致性,还包括脏页的刷新、合并插入缓存、undo页的回收。

IO Thread

在InnoDB 存储引擎中大量使用了AIO 来处理IO 请求,这样可以极大地提高数据库的性能,从IO Thread 主要负责这些IO 请求的回调。

线程类型默认个数职责
Read thread4负责读操作
Write thread4负责写操作
Log thread1负责将日志缓冲区刷新到磁盘
Insert buffer thread1负责将写缓冲区内容刷新到磁盘
Purge Thread

主要用于回收事务已经提交了的undo log,在事务提交之后,undo log 可能不用了,就用它来回收

Page Cleaner Thread

协助 Master Thread 刷新脏页到磁盘的线程,他可以减轻Master Thread 的工作压力,减少阻塞。

当我们业务在操作的时候会直接操作MySQL 的缓冲区,如果缓冲区内没有数据,会将磁盘的数据加载进来,然后存储在缓冲区当中。在增删改查的时候会操作这个缓冲区里的数据。缓冲区的数据会以一定的频率,一定的时机通过后台线程刷新到磁盘当中,然后才持久化

5.3 事务原理

InnoDB 引擎很重要的一部分就是支持事务。

事务是一组操作的集合,他是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同事成功,要么同事失败。

特性

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
  • 持久性(Durability):事务一旦提交或回滚,它对数据库的数据改变就是永久的。

在这里插入图片描述

redo log

我们所讲到的ACID 的持久性就是通过redo log 来保证的。

重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。

该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,而后者在磁盘中当事务提交之后会把所有修改信息都存到该日志文件中,用于刷新脏页到磁盘,发生错误时,进行数据恢复使用。

在这里插入图片描述

redo log 保证持久性。

undo log

回滚日志,用于记录数据被修改前的信息,作用包含2个:提供回滚和MVCC(多版本并发控制)。

undo log 和 redo log 记录物理日志不一样,它是逻辑日志。可以认为当delete 一条记录时,undo log 中会记录一条对应的insert 的记录,当update 一条记录时,它记录一条相对应相反的update 记录。当执行rollback 时,就可以从undo log 中逻辑记录读取到相应的内容并进行回滚。

undo log 销毁:undo log 在事务执行时,并不会立即删除undo log ,因为这些日志可能还用于MVCC 。

undo log 存储:undo log 采用段的方式进行管理记录。存放在rollback segment 回滚段中,内部包含了1024个undo log segment 。

undo log 保证事务的原子性,而undo log + redo log 保证事务的一致性。

5.4 MVCC

基本概念

当前读

读取的是当前记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。如:select lock in share modeselect for updateupdateinsertupdate,delete(排他锁)都是一种当前读。

快照读

简单的select(不加锁)就是快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。

  • RC 每次select,都生成快照读(因为每次都生成快照,所以会读到其他事务提交)。
  • RR 开启事务第一个select 语句才是快照读。
  • Serializable 快照读会退化为当前读。
MVCC

Mulit-Version Concurrency Control 多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL 实现的MVCC 提供了一个非阻塞读功能。MVCC 的具体实现,还需要依赖于数据库记录中的三个隐式字段、unlog 日志 readview。

隐藏字段

InnoDB 在创建表的时候会多创建2个字段,分别是DB_TRX_IDDB_ROLL_PTRDB_ROW_ID

隐式字段含义
DB_TRX_ID最近修改事务ID,记录插入这条记录或者最后修改该记录的事务ID
DB_ROLL_PTR回滚指针,指向这条记录的上一个版本,用于配合undo log ,指向上一个版本
DB_ROW_ID隐藏主键,如果表结果没有指定主键,将会生成该隐藏字段。

undo log 版本链

回滚日志,在insert update delete 的时候产生的便于数据回滚的日志。

当insert 的时候,产生的undo log 日志只在回滚时需要,在事务提交后,可被立即删除。

而update、delete 的时候产生的undo log 日志不仅在回滚是需要,在快照读时也需要,不会被立即删除。

undo log 版本链

在这里插入图片描述

不同事务或者相同事务对同一条记录进行修改,会导致该记录的undo log 生成一条记录版本链表,链表的头部是最新的旧记录,尾部是最旧的记录。

readview 介绍

读视图是快照读SQL 执行时MVCC 提取数据的一句,记录并维护系统当前活跃的事务(未提交的)ID

read view 包括了四个核心字段:

字段含义
m_ids当前活跃的事务ID集合
min_trx_id最小活跃事务ID
max_trx_id预分配事务ID,当前最大事务ID+1(事务ID是自增的)
creator_trx_idRead View 创建者的事务ID

$$ 版本链数据访问规则 \begin

  1. trx—id=creator—trx—id, &可以访问该版本 (当前事务更改的)\
  2. trx—id < min—trx—id, &可以访问该版本(数据已经提交)\
  3. trx—id > max—trx—id, &不可以访问该版本(当前事务是在ReadView 生成之后开启的)\
  4. min—trx—id \le trx—id \le max—trx—id, &如果trx—id不在m-ids中是可以访问该版本的(事务已经提交) \end{cases} $$

不同的事务隔离级别,生成的readview 的时机不同。

RC 每次都生成 readview ,RR 只在事务第一次执行快照读的时候生成readview,后续复用该readview。

原理分析(RC)

RC 每次都生成 readview 。

在这里插入图片描述

原理分析(RR)

事务在第一次执行快照读时生成ReadView,后续复用该ReadView。

在这里插入图片描述

MVCC 主要还是通过隐藏字段(事务id,回滚指针)、undo log 版本链,readview 实现,MVCC + 锁保证了事务的隔离性。

Released under the MIT License.