Mysql
# 如何创建一个表
CREATE TABLE IF NOT EXISTS `runoob_tbl`(
`runoob_id` INT UNSIGNED AUTO_INCREMENT,
`runoob_title` VARCHAR(100) NOT NULL,
`runoob_author` VARCHAR(40) NOT NULL,
`submission_date` DATE,
PRIMARY KEY ( `runoob_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
# 增加
insert into Strdents (姓名,性别,出生日期) values ('王伟华','男','1983/6/15');
# 删除
delete from a where name='王伟华'(删除表a中列值为王伟华的行);
# 更新
update addressList set 年龄=18 where 姓名='王伟华';
# 查询
select name as 姓名 from a where gender='男';
# 通过CMD连接数据库
mysql -uroot -p123456
# 使用对应的数据库
use database;
# 展示对应的表格内容
show table;
mysql的常见数据类型:
类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 |
---|---|---|---|---|
TINYINT | 1 byte | (-128,127) | (0,255) | 小整数值 |
SMALLINT | 2 bytes | (-32 768,32 767) | (0,65 535) | 大整数值 |
INT或INTEGER | 4 bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 |
DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 |
DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 |
类型 | 大小 | 用途 |
---|---|---|
CHAR | 0-255 bytes | 定长字符串 |
VARCHAR | 0-65535 bytes | 变长字符串 |
1.事务
事务的基本特性
事务是数据库中的一个或者一系列操作的最小逻辑单元。
- A 原子性:要么不执行,要么完全执行,不能执行一部分;undo log
- C 一致性:整体数据的完整性和一致性; MVCC
- I 隔离性:多个事务并发执行时互相隔离; 锁
- D 永久性:事务一旦成功提交,只要修改的数据就会进行持久化存储,不会因为异常、宕机就丢失。 redo log
事务的四种隔离级别
隔离级别越低,效率越高,但是安全性就越低
隔离级别越高,效率越低,安全性越高。
Read unocommited. 读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据,会导致脏读。
Read committed 读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据,会导致不可重复读。
Repeatable read 重复读,就是在开始读取数据(事务开启)时,不再允许修改操作,但是无法解决Insert等操作。(这是InnoDB的默认隔离级别)
Serializable 序列化 Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
如何理解脏读、不可重复读、幻读等操作
脏读:假设我的工资是5k一个月,今天发工资了我去查了一下发现到账6k,然后财务发现了钱打错了,进行了rollback操作,我又查的时候发现还是5k(TvT)。这是由于我在事务的过程中的读取到并未写入表中的内容,读到的是可能会被修改的数据,所以称之为脏读。
不可重复读:还是我,第二天工资卡里面有5k元,我去买台4500的电脑,然后在消费的时候收银人员说有5000元,让我输密码,输完密码显示没钱了,原来是别人拿着我的卡买了别的东西,卡内余额不够支付了。
幻读:还是我,月末了去银行拉个账单,查询一下,有19条消费记录,我寻思一下打印下来,结果出来了20条记录,这跟幻觉一样就叫做幻读。
Innodb是如何实现事务的?
Innodb通过Buffer Pool、LogBuffer、Redo Log、Undo Log来实现事务,以一个update语句为例:
- Innodb在收到一个update语句后,会根据条件找到数据所在的页,并将该页缓存在Buffer Pool内;
- 执行Update语句,修改Buffer Pool中的数据,也就是内存中的数据;
- 针对Update操作生成对应的RedoLog对象,并存入LogBuffer中;
- 针对Update操作生成对应的Undolog对象,用于事务回滚;
- 如果事务提交,就把RedoLog对象进行持久化,后续还有其他机制将Buffer Pool中所修改的数据持久化到磁盘中
- 如果事务回滚,则利用Undolog进行回滚。
3.锁
数据库的锁是为了解决事务的隔离性,让事物之间不互相影响。
按锁粒度从大到小分类:表锁,页锁和行锁;以及特殊场景下使用的全局锁
如果按锁级别分类则有:共享(读)锁、排他(写)锁、意向共享(读)锁、意向排他(写)锁;
以及Innodb引擎为解决幻读等并发场景下事务存在的数据问题,引入的Record Lock(行记录锁)、Gap Lock(间隙锁)、Next-key Lock(Record Lock + Gap Lock结合)等;
还有就是我们面向编程的两种锁思想:悲观锁、乐观锁。
开销 | 速度 | 死锁可能 | 粒度 | 并发性能 | |
---|---|---|---|---|---|
表锁 | 小 | 快 | 无 | 大 | 低 |
行锁 | 大 | 慢 | 会 | 小 | 高 |
页锁 | 中 | 中 | 会 | 中 | 中 |
锁类型 | 读锁 | 写锁 | 意向读锁 | 意向写锁 |
---|---|---|---|---|
读锁 | 兼容 | 冲突 | 兼容 | 冲突 |
写锁 | 冲突 | 冲突 | 冲突 | 冲突 |
意向读锁 | 兼容 | 冲突 | 兼容 | 兼容 |
意向写锁 | 冲突 | 冲突 | 兼容 | 兼容 |
# 共享锁
SELECT … LOCK IN SHARE MODE;
#
数据库的锁是为了解决事务的隔离性,让事物之间不互相影响。
分类:
按照锁粒度分为表锁、页锁、行锁,特殊场景下使用全局锁
按照锁的级别分为共享锁(读)、排他锁(写)、意向共享锁、意向排他锁
面向编程的两种锁思想:悲观锁、乐观锁
表锁:
是MySQL各种引擎中最大粒度的锁定机制。
获取锁和释放锁的速度很快;解决死锁问题。
资源争用的概率很高,大大降低并发力度。
使用场景:适合大量按索引条件并发更新数据的情况,同时又有并发查询的情况。
使用表级锁的是myisam\memory等。
行锁:
最小颗粒度的锁定机制。
发生锁定资源竞争的概率最小,并发处理能力高。
可以避免脏读的发生:对这个行上锁,如果修改完数据,没有提交,别人只能看见之前的数据,只有在提交完了之后别人才能看见或者是对该行进行修改。
颗粒度小导致每次获取锁和释放锁需要做的事很多,消耗大。容易发生死锁。
使用场景:适合以查询为主、只有少量按索引条件更新数据的应用。
使用行级锁定的主要是InnoDB存储引擎。
页锁:
页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
使用页级锁定的主要是BerkeleyDB存储引擎。
间隙锁:
范围查询会产生间隙锁,如果往间隙里面插入数据是插不进去的
如果范围查询一万行的数据,那么一万行数据都被锁住了,这一万行数据里面都是操作不了的。
全局锁:
是对整个数据库实例加锁。使用场景一般在全库逻辑备份时。
- Flush tables with read lock (FTWRL)
如果在主库备份,在备份期间不能更新,业务停摆
如果在从库备份,备份期间不能执行主库同步的binlog,导致主从延迟同步
如果客户端突然断开,MYSQL会自动释放这个全局锁
- 还有一种锁全局的方式:
set global readonly=true
,相当于将整个库设置成只读状态
如果客户端突然断开,数据库依然是只读状态,并将长期处于只读不可写的状态
共享锁S与排他锁X:
共享锁又称为读锁,是可以共享的;在查询语句后面增加LOCK IN SHARE MODE
排他锁又称为写锁,是不可以共享的;在查询语句后面增加FOR UPDATE
意向锁:
属于表级锁,在给事务加锁之前,说明这个锁的类型。
INNODB中的两个表锁:
意向共享锁IS:表示准备给数据行加上共享锁,也就是在给一个数据行加共享锁之前必须取得该表的IS
意向排他锁IX:表示准备给数据行加上排他锁
意向锁是innodb自动加的,不需要用户干预。
悲观锁与乐观锁:
并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。
乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就**把事务锁起来,直到提交事务。**实现方式:使用数据库中的锁机制。适合于写较多的情况。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。适合于写较少的情况,省去了所得开销,加大了系统的吞吐量。
死锁
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
常见的解决死锁的方法
1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
4.数据库的三大范式
- 第一范式:确保每列保持原子性,数据表中的所有字段值都是不可分解的原子值。
- 第二范式:对于没有包含在主键中的列(非主键的其他列)必须完全依赖于主键,而不能只依赖于主键的一部分(比如某一个主键)。
- 第三范式:确保每列都和主键列直接相关而不是间接相关
5.MYSQL索引原理
https://www.cnblogs.com/aspirant/p/9214485.html
Innodb和Myisam,Innodb会保存两个文件,分别是索引.frm和数据.idb;再Myisam中会生成三个文件,分别是索引.frm、地址.myi和数据.myd。
把无序数据变得有序,以便于快速寻找具有特定值的数据。
- 把创建了索引的内容进行排序;
- 对排序结果形成倒排表(这是从用户的角度出发的,只能想到某些关键词,物品属性——物品,关键词——文档);
- 在倒排表后面加上地址链接;
- 用户在查询的时候,先根据倒排表查到地址链接再找到文档。
索引类型
- 哈希索引
- 在Mysql中的Memory存储引擎中就是使用哈希索引
- hash存在hash碰撞问题,需要设计一个比较好的扰动算法来辅助
- 会占用一个比较的内存空间用来存储哈希索引
- 只能用于等值查询,不能用于范围查询
- B+树将所有的数据都存放在叶子节点中,但是B树是存在每个节点中的。B+在非子节点中没有数据。对于顺序访问更有效果。
索引类型
- 主键索引
- 唯一索引
- 普通索引、辅助索引、二级索引
- 全文索引
- 组合索引、联合索引
回表:
由于Innodb对索引的底层都是采用B+树,那意味着在叶子节点中就会保存数据,为了防止每次数据都因为索引的原因重新复制一次数值,因此Innodb只会在主键索引中的叶子节点中存放对应的数值,其他的索引都只是保存部分信息。如果通过普通索引得到对应的主键数值,然后如果查询通过主键来进行第二次查询,所以这种主要去主键索引中进行数据查找的过程叫做回表。
最左匹配原则:
适用于组合索引的情况(假设我们在name、age创建组合索引)
必须先匹配到第一个列,然后匹配到到第二个列,然后匹配到第三个列
select * from table where name=? and age = ? # √
select * from table where name=? # √
select * from table where age = ? # ×
select * from table where age=? and name = ? # √ (这是因为mysql中存在优化器可以帮助实现条件顺序的优化)
索引下推:
将一些原本放在server层中完成的操作下推给存储引擎中去完成。(存在在联合索引的使用过程中)
索引下推是mysql5.7之后采用的特征
select * from table where name=? and age = ?
执行上述操作时,
没有索引下推之前:
根据name的值去存储引擎中把数据取出来,然后再server层中去做age的条件筛选
索引下推之后:
根据name和age两个列的值去存储引擎中做数据筛选,不需要再server中再去做条件过滤了,相当于将查询的消耗分担到存储时了。
索引设计的原则:
- 适合索引的列。是出现在where语句中的列,或者是用于连接语句中的列。
- 不适合建立索引的情况。在基数较小的类上索引的效果较差,没有必要在此列上建立索引。
- 使用短索引。如果对一个长的字符串进行索引,应该制定前缀长度,这样可以大量减少索引空间。
- 不要过度索引。因为索引需要额外的磁盘空间,会降低写操作的执行,修改表的时候,索引也需要更新甚至是重构,索引的列越多,这个时间就越长,因此建立索引只要满足需求即可。
索引创建的原则:
- 最左前缀匹配原则。是组合索引中非常重要的原则,它会一直向右匹配,直到遇到范围查询(> < between like)才会停止。
- 较为频繁且作为查询条件的字段创建索引。
- 定义为外键的数据列一定要添加索引。
- 较为频繁更新的字段不适合创建索引。
- 不能有效区分数据字段不适合创建索引。如:性别就不适合做索引。
- 重复值很多的列、查询中很少涉及的列不适合创建索引。
- 定义为text image bit数据类型的列不要创建索引。
- 尽量扩展索引,而不是新建索引。如:已经有a的索引,要一个(a,b)的索引,直接添加就行。
要注意:
- 非空字段。含有空值的列很难进行查询优化
- 取离散值大的字段。(count函数可以查看字段的差异值,返回值越大说明唯一值越多,离散值越大)
- 索引字段越小越好。数据库的数据存储以页为单位,一页存储的数据越多,一次IO能获取的数据越多,效率越高。
创建索引的三种方法:
-
创建表的时候就添加索引create table table_name(字段 类型 约束, key key_name 字段/(字段1,字段2))
-
用alter添加索引(主键、唯一键、普通键)
alter table table_name add index index_name (column_name)
- 用create创建索引(唯一键、普通键,但是key_name不能加主键)
Create index index_name on table_name (column_name)
删除索引的方法:
Alter table table_name drop key 索引名
如果主键没有自增长:alter table table_name drop primary key
如果主键自增长:不能执行此操作(自增长依赖主键索引,要先删除自增(重新定义列类型就行),再删除)
索引失效的场景:
-
有or必全有索引;
解决方法:如果or前后是同一个索引,其实是可以命中索引的;可以用union或者union all来完成对应的操作。
-
复合索引未用左列字段;
不符合最左前缀方法
-
like以%开头;
解决方法:利用覆盖索引,只select对应索引的内容;将%放在放在模糊检索的后面。
-
需要类型转换;
比如:建表的时候为varchar,但是查询的时候采用的整型进行查询。
解决方法:将字符编码格式统一。
-
where中索引列有运算或者负向查询;
负向查询包括:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等。
解决方法:尽量在应用程序中进行计算和转换。
-
where中索引列使用了函数;
解决方法:尽量在应用程序中进行计算和转换。
-
如果mysql觉得全表扫描更快时(数据少);
6、Drop、Delete、Truncate之间的区别
- delete从表中删除某一行的数据,并将操作放入事务中,是可以通过rollback进行回滚的
- Truncate一次性从表中删除所有数据,但是保留数据表的结构,操作不可回滚,执行速度较快
- Drop删除表内数据机器结构,并将其所占用的空间进行释放
- Truncate和Delete是针对数据进行操作的指令,而Drop直接删除表结构
- Delete属于DML,Truncate和Drop属于DDL语言不可回滚
- 在速度上:Drop>Truncate>Detele
7、一条Sql语句执行的很慢是什么原因?
- 偶尔很慢:
- 可能由于mysql数据库正在执行持久化操作,将redo.log中操作保存到硬盘中
- 遇到了锁操作
- 一致很慢:
- 没有使用上索引,一直都在做全表索引操作
- 数据库选错了索引
8、为什么用自增列作为主键
- 如果我们定义了主键(Primarily Key),那么InnoDB会选择主键作为聚集索引。如果没有定义主键,那么Innodb会寻找第一个不包含有NULL值的唯一索引作为主键索引。
- 数据记录本身存于主索引(一颗B+树)的叶子节点上,这就要求同一个叶子节点内的各条数据应该按照顺序存放。这时候当页面中数据数量达到装载因子后就会开辟信的一页。
- 如果表使用自增主键,那么每次插入新的记录,记录就会顺序的添加到当i请安为止,当一页写满后就开辟新的一页(索引就是一张额外的表),但是如果采用非自增索引(例如身份证号或者学号),由于每次插入主键的值近乎随机,因此新纪录可能在新的页和老页中来回穿梭。
9、Inner Join、Left Join、Right Join他们的区别是什么?
Inner Join就是可以认为是简单的查询,只是输出两个表的交集部分
Left Join是以左边的表作为主表,即左表的信息全部展示,右表只展示与左表内容一致。
Right Join是以右表作为主表,即右表中的所有信息全部展示,左表中只显示与右表中关键字相同的内容,并附加到后面。
10、为什么使用索引可以提高效率?
- 数据索引的存储是有序的
- 在有序的情况下,通过索引查询一个数据时无需遍历索引记录的,可以直接位置来得到,类似于Python中的有序字典的思路
- 极端情况下,数据索引的查询效率为二分法查询效率,趋近于log2(N)
11、count(*)、count(1)、count(列名)的区别
- 从结果上而言:
- *与1没有差距,都是统计表中数据的个数,并且都会将空值考虑进去
- 但是列名就会抛弃掉空值的存在
- 从效率上而言:
- 如果表中有主键,那么count(主键)效率最高,否则1的效率最高
- 如果表中只有一列,那么*的效率高于1
- 其他情况都是1最高,因为*需要mysql帮助其转换到某一个列名,而1就可以直接运行。
12、UNION和UNION ALL的区别?
Union因为要进行重复值扫描,所以效率低。如果合并没有刻意要删除重复行,那么就使用Union All
两个要联合的SQL语句 字段个数必须一样,而且字段类型要“相容”(一致);
如果我们需要将两个select语句的结果作为一个整体显示出来,我们就需要用到union或者union all关键字。union(或称为联合)的作用是将多个结果合并在一起显示出来。union和union all的区别是,union会自动压缩多个结果集合中的重复结果,而union all则将所有的结果全部显示出来,不管是不是重复。
Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;
Union All:对两个结果集进行并集操作,包括重复行,不进行排序;
13、聚簇索引和非聚簇索引有什么区别?什么情况下用聚簇索引?
- 聚簇索引:将数据存储与索引放在一起,找到索引也就可以找到数据了。Innodb引擎。
- 非聚簇索引:将数据存储和索引分开存放,索引结构的叶子节点只想了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时,在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key_buffer命中时,速度慢的原因。
14、存储过程
将一些操作或者sql语句封装成一个存储过程,可以保护银行(或者其他数据结构为主)的数据库设计结构不被外包公司知晓,保障数据信息安全。
优点:
- 存储过程时经过编译的sql脚本,所以执行的速度会非常快
- 实现了SQL编程,可以降低锁表的时间和锁表的范围
- 对外封装表结构,提升数据库的安全性
在数据库中添加一万条数据。
DELIMITER $$ 将结束定义符设置为$$,可能时防止和函数内定义的方法重合
DROP PROCEDURE IF EXISTS `proc_auto_insertdate` $$
CREATE PROCEDURE `proc_auto_insertdate`()
BEGIN
DECLARE pid INTEGER DEFAULT 4;
WHILE pid <= 10000 DO
INSERT account VALUES(pid,CONCAT("test",pid),pid+1000);
SET pid = pid + 1;
END WHILE;
END $$
DELIMITER ;
CALL proc_auto_insertdate();
存储过程与函数的联系与区别
相同点:创建与存储结构相似,都可以携带多个传入参数和传出参数;一次编译,多次执行
不同点:
- 存储过程用于完成特定操作或任务,函数用于返回特定数据
- 定义存储过程用procedure,函数定义用function
- 存储过程不能用return返回值,只能用in/out返回值,函数必须要有return,也可以使用in/out
- 存储函数可以返回多个值,也可以没有返回值,函数只能返回一个值
15、InnoDB和MyISAM的区别?
InnoDB
支持事务处理
支持外键
支持表锁和行锁
DETELE表时,是一行一行的删除
InnoDB索引的叶子节点直接存放的是数据,而MyISAM存放的是地址
MyISAM
不支持事务
不支持外键
支持表锁
DETELE表时,是先drop表,然后重建表
16、视图
本质上是一种虚拟表,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以储存的数据值形式存在。
只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高了数据库中数据的安全性。
根本用途:简化SQL查询,提高开发效率。/兼容老的表结构
特点:
- 字段可来自不同的表
- 由基本表产生的虚表
- 建立和删除不影响基本表
- 对视图内容的更新(添加/修改/删除)直接影响基本表
- 视图来自多个基本表时,不允许添加和删除数据
作用/优点:
- 简化操作,提高重用性,可以把经常使用的数据通过SQL操作定义为视图。(以后只要select * from view就行)
- 提高安全性,用户只能查询和修改视图中看到的数据
- 逻辑上具有独立性,屏蔽了真实表结构带来的影响
缺点:
- 性能方面:数据库要把视图的查询转化成对基本表的查询,如果视图由多个表定义,那么查询性能差。
- 修改限制:用户视图修改视图的某些行时,数据库要将其转化为对某些行的修改。对于复杂的视图,可能是不可修改的。
视图和表的区别
- 视图是编译好的SQL语句,表不是
- 视图没有实际的物理记录、不占用物理内存,而表有
- 视图是局部模式的表,是虚表,表是全局模式的表,是实表
- 视图是窗口,而表有内容
- 视图是逻辑概念的存在,只能用创建语句来修改,不能进行update或insert into等操作
- 表是三级模式结构中的概念模式,视图是外模式
- 视图可以不给用户接触数据表,从而不知道表结构
17、MVCC多版本并发控制
Multi-Version Concurrency Control
在数据库中,并发控制是指在多个用户/进程/线程同时对数据库进行操作时,如何保证事务的一致性和隔离性,同时最大程度地开发。
当多个用户/进程/线程对数据库进行操作时,根据他们的操作可以分成如下几类:
- 读读,不存在任何问题,也不需要并发控制
- 读写,有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
- 写写,有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
MVCC 在 MySQL InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
-
当前读
像select lock in share mode(共享锁),select for update;update;insert;detele(排他锁)这些操作都是一种当前读,为什么要当前读?因为这种操作读取的数据是记录中的最新版本,读取还要保证其他并发事务不能修改当前记录,会对读取的记录加锁。
-
快照读
像是select操作就是快照读,就是不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;MVCC可以认为是行锁的变种,避免了加锁操作,降低了开销;既然是基于多版本,快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
MVCC 带来的好处是?
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
- 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
MVCC的实现原理
MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的。所以我们先来看看这个三个 point 的概念
隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID, DB_ROLL_PTR, DB_ROW_ID 等字段
DB_TRX_ID
DataBase_Transaction_ID 事务ID,6 byte,最近修改(修改/插入)事务 ID:记录创建这条记录/最后一次修改该记录的事务 ID
DB_ROLL_PTR
DataBase_Rollback_Pointer 回滚指针,7 byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里)
DB_ROW_ID
DataBase_Row_ID,6 byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引
实际还有一个删除 flag 隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除 flag 变了
如上图,DB_ROW_ID
是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID
是当前操作该记录的事务 ID ,而 DB_ROLL_PTR
是一个回滚指针,用于配合 undo日志(undo log),指向上一个旧版本。