赞
踩
更多golang面试题戳文末链接
可以从三个维度回答这个问题:索引哪些情况会失效,索引不适合哪些场景,索引规则
我排查死锁的一般步骤是酱紫的:
可以从这几个维度回答这个问题:
分库分表方案,分库分表中间件,分库分表可能遇到的问题
select count(*) from table
时,MyISAM更快,因为它有一个变量保存了整个表的总行数,可以直接读取,InnoDB就需要全表扫描。.frm
、.MYD
、.MTI
来储存表定义,数据和索引。可以从几个维度去看这个问题,查询是否够快,效率是否稳定,存储数据多少,以及查找磁盘次数,为什么不是二叉树,为什么不是平衡二叉树,为什么不是B树,而偏偏是B+树呢?
如果二叉树特殊化为一个链表,相当于全表扫描。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。
我们知道,在内存比在磁盘的数据,查询效率快得多。如果树这种数据结构作为索引,那我们每查找一次数据就需要从磁盘中读取一个节点,也就是我们说的一个磁盘块,但是平衡二叉树可是每个节点只存储一个键值和数据的,如果是B树,可以存储更多的节点数据,树的高度也会降低,因此读取磁盘的次数就降下来啦,查询效率就快啦。
何时使用聚集索引或非聚集索引?
方案一:如果id是连续的,可以这样,返回上次查询的最大记录(偏移量),再往下limit
select id,name from employee where id>1000000 limit 10.
方案二:在业务允许的情况下限制页数:
建议跟业务讨论,有没有必要查这么后的分页啦。因为绝大多数用户都不会往后翻太多页。
方案三:order by + 索引(id为索引)
select id,name from employee order by id limit 1000000,10
SELECT a.* FROM employee a, (select id from employee where 条件 LIMIT 1000000,10 ) b where a.id=b.id
方案四:利用延迟关联或者子查询优化超多分页场景。(先快速定位需要获取的id段,然后再关联)
Mysql默认的事务隔离级别是可重复读(Repeatable Read)
要安全的修改同一行数据,就要保证一个线程在修改时其它线程无法更新这行记录。一般有悲观锁和乐观锁两种方案~
悲观锁思想就是,当前线程要进来修改数据时,别的线程都得拒之门外~
比如,可以使用select…for update ~
select * from User where name=‘jay’ for update
以上这条sql语句会锁定了User表中所有符合检索条件(name=‘jay’)的记录。本次事务提交之前,别的线程都无法修改这些记录。
乐观锁思想就是,有线程过来,先放过去修改,如果看到别的线程没修改过,就可以修改成功,如果别的线程修改过,就修改失败或者重试。实现方式:乐观锁一般会使用版本号机制或CAS算法实现。
悲观锁她专一且缺乏安全感了,她的心只属于当前事务,每时每刻都担心着它心爱的数据可能被别的事务修改,所以一个事务拥有(获得)悲观锁后,其他任何事务都不能对数据进行修改啦,只能等待锁被释放才可以执行。
乐观锁的“乐观情绪”体现在,它认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。实现方式:乐观锁一般会使用版本号机制或CAS算法实现。
show status
命令了解各种 sql 的执行频率explain
分析低效 sql 的执行计划(这点非常重要,日常开发中用它分析Sql,会大大降低Sql导致的线上事故)select查询语句是不会加锁的,但是select for update除了有查询的作用外,还会加锁呢,而且它是悲观锁哦。至于加了是行锁还是表锁,这就要看是不是用了索引/主键啦。
没用索引/主键的话就是表锁,否则就是是行锁。
某个表有近千万数据,可以考虑优化表结构,分表(水平分表,垂直分表),当然,你这样回答,需要准备好面试官问你的分库分表相关问题呀,如
除了分库分表,优化表结构,当然还有所以索引优化等方案~
复合索引,也叫组合索引,用户可以在多个列上建立索引,这种索引叫做复合索引。
当我们创建一个组合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。
select * from table where k1=A AND k2=B AND k3=D
有关于复合索引,我们需要关注查询Sql条件的顺序,确保最左匹配原则有效,同时可以删除不必要的冗余索引。
这个,跟一下demo来看更刺激吧,啊哈哈
假设表A表示某企业的员工表,表B表示部门表,查询所有部门的所有员工,很容易有以下SQL:
select * from A where deptId in (select deptId from B);
这样写等价于:
先查询部门表B
select deptId from B
再由部门deptId,查询A的员工
select * from A where A.deptId = B.deptId
可以抽象成这样的一个循环:
List<> resultSet ;
for(int i=0;i<B.length;i++) {
for(int j=0;j<A.length;j++) {
if(A[i].id==B[j].id) {
resultSet.add(A[i]);
break;
}
}
}
显然,除了使用in,我们也可以用exists实现一样的查询功能,如下:
select * from A where exists (select 1 from B where A.deptId = B.deptId);
因为exists查询的理解就是,先执行主查询,获得数据后,再放到子查询中做条件验证,根据验证结果(true或者false),来决定主查询的数据结果是否得意保留。
那么,这样写就等价于:
select * from A,先从A表做循环
select * from B where A.deptId = B.deptId,再从B表做循环.
同理,可以抽象成这样一个循环:
List<> resultSet ;
for(int i=0;i<A.length;i++) {
for(int j=0;j<B.length;j++) {
if(A[i].deptId==B[j].deptId) {
resultSet.add(A[i]);
break;
}
}
}
数据库最费劲的就是跟程序链接释放。假设链接了两次,每次做上百万次的数据集查询,查完就走,这样就只做了两次;相反建立了上百万次链接,申请链接释放反复重复,这样系统就受不了了。即mysql优化原则,就是小表驱动大表,小的数据集驱动大的数据集,从而让性能更优。
因此,我们要选择最外层循环小的,也就是,如果B的数据量小于A,适合使用in,如果B的数据量大于A,即适合选择exists,这就是in和exists的区别。
使用自增主键对数据库做分库分表,可能出现诸如主键重复等的问题。解决方案的话,简单点的话可以考虑使用UUID哈
自增主键会产生表锁,从而引发问题
自增主键可能用完问题。
MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从而提高并发性能的一种机制。
sharding-jdbc目前是基于jdbc驱动,无需额外的proxy,因此也无需关注proxy本身的高可用。
Mycat 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库,而 Sharding-JDBC 是基于 JDBC 接口的扩展,是以 jar 包的形式提供轻量级服务的。
嘻嘻,先复习一下主从复制原理吧,如图:
主从复制分了五个步骤进行:
一个服务器开放N个链接给客户端来连接的,这样有会有大并发的更新操作, 但是从服务器的里面读取binlog的线程仅有一个,当某个SQL在从服务器上执行的时间稍长 或者由于某个SQL要进行锁表就会导致,主服务器的SQL大量积压,未被同步到从服务器里。这就导致了主从不一致, 也就是主从延迟。
数据库连接池原理:在内部对象池中,维护一定数量的数据库连接,并对外暴露数据库连接的获取和返回方法。
先看一下Mysql的逻辑架构图吧~
Bytes_received和Bytes_sent 和服务器之间来往的流量。
Com_*服务器正在执行的命令。
Created_*在查询执行期限间创建的临时表和文件。
Handler_*存储引擎操作。
Select_*不同类型的联接执行计划。
Sort_*几种排序信息。
如果按锁粒度划分,有以下3种:
Mysql逻辑架构图主要分三层:
MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。
有三种格式哈,statement,row和mixed。
优点:
缺点:
在执行CREATE TABLE时创建索引
CREATE TABLE `employee` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`date` datetime DEFAULT NULL,
`sex` int(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
使用ALTER TABLE命令添加索引
ALTER TABLE table_name ADD INDEX index_name (column);
使用CREATE INDEX命令创建
CREATE INDEX index_name ON table_name (column);
在B树中,键和值即存放在内部节点又存放在叶子节点;在B+树中,内部节点只存键,叶子节点则同时存放键和值。
B+树的叶子节点有一条链相连,而B树的叶子节点各自独立的。
B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。.
B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据。innodb中页的默认大小是16KB,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快.
动作描述 | 使用聚集索引 | 使用非聚集索引 |
---|---|---|
列经常被分组排序 | 应 | 应 |
返回某范围内的数据 | 应 | 不应 |
一个或极少不同值 | 不应 | 不应 |
小数目的不同值 | 应 | 不应 |
大数目的不同值 | 不应 | 应 |
频繁更新的列 | 不应 | 应 |
外键列 | 应 | 应 |
主键列 | 应 | 应 |
频繁修改索引列 | 不应 | 应 |
不一定,如果查询语句的字段全部命中了索引,那么就不必再进行回表查询(哈哈,覆盖索引就是这么回事)。
举个简单的例子,假设我们在学生表的上建立了索引,那么当进行select age from student where age < 20
的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。
组合索引,用户可以在多个列上建立索引,这种索引叫做组合索引。
因为InnoDB引擎中的索引策略的最左原则,所以需要注意组合索引中的顺序。
数据库事务(简称:事务),是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
回答这个问题,可以先阐述四种隔离级别,再阐述它们的实现原理。隔离级别就是依赖锁和MVCC实现的。
从锁的类别上来讲,有共享锁和排他锁。
锁兼容性如下:
基于索引来完成行锁的。
select * from t where id = 666 for update;
for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么InnoDB将实行表锁。
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。看图形象一点,如下:
死锁有四个必要条件:互斥条件,请求和保持条件,环路等待条件,不剥夺条件。
解决死锁思路,一般就是切断环路,尽量避免并发形成环路。
为了提高复杂SQL语句的复用性和表操作的安全性,MySQL数据库管理系统提供了视图特性。
视图是一个虚拟的表,是一个表中的数据经过某种筛选后的显示方式,视图由一个预定义的查询select语句组成。
视图用途: 简化sql查询,提高开发效率,兼容老的表结构。
游标提供了一种对从表中检索出的数据进行操作的灵活手段,就本质而言,游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。
存储过程,就是一些编译好了的SQL语句,这些SQL语句代码像一个方法一样实现一些功能(对单表或多表的增删改查),然后给这些代码块取一个名字,在用到这个功能的时候调用即可。
优点:
缺点:
触发器,指一段代码,当触发某个事件时,自动执行这些代码。
使用场景:
MySQL 数据库中有六种触发器:
字段最多存放 50 个字符
如 varchar(50) 和 varchar(200) 存储 “jay” 字符串所占空间是一样的,后者在排序时会消耗更多内存
delete | truncate | drop | |
---|---|---|---|
类型 | DML | DDL | DDL |
回滚 | 可回滚 | 不可回滚 | 不可回滚 |
删除内容 | 表结构还在,删除表的全部或者一部分数据行 | 表结构还在,删除表中的所有数据 | 从数据库中删除表,所有的数据行,索引和权限也会被删除 |
删除速度 | 删除速度慢,逐行删除 | 删除速度快 | 删除速度最快 |
列值为NULL也是可以走索引的
计划对列进行索引,应尽量避免把它设置为可空,因为这会让 MySQL 难以优化引用了可空列的查询,同时增加了引擎的复杂度
优化慢查询:
如果是单机的话,选择自增ID;如果是分布式系统,优先考虑UUID吧,但还是最好自己公司有一套分布式唯一ID生产方案吧。
自增主键一般用int类型,一般达不到最大值,可以考虑提前分库分表的。
自增ID用完后 一直都是最大值 如果标识了主键 则主键冲突
null值会占用更多的字节,并且null有很多坑的。
密码散列,盐,用户身份证号等固定长度的字符串,应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率。
这个jar包: mysql-connector-java-5.1.18.jar
Mysql驱动程序主要帮助编程语言与 MySQL服务端进行通信,如连接、传输数据、关闭等。
平时积累吧:
排查过程:
处理:
其他情况:
也有可能是每个 sql 消耗资源并不多,但是突然之间,有大量的 session 连进来导致 cpu 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等
主从复制原理,简言之,就三步曲,如下:
如下图所示:
上图主从复制分了五个步骤进行:
存储精度都为秒
区别:
Explain 执行计划包含字段信息如下:分别是 id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered、Extra 等12个字段。
我们重点关注的是type,它的属性排序如下:
system > const > eq_ref > ref > ref_or_null >
index_merge > unique_subquery > index_subquery >
range > index > ALL
监控的工具有很多,例如zabbix,lepus,我这里用的是lepus
主从一致性校验有多种工具 例如checksum、mysqldiff、pt-table-checksum等
更换字符集utf8–>utf8mb4
SELECT CURRENT_DATE();
1、如果A表TID是自增长,并且是连续的,B表的ID为索引
select * from a,b where a.tid = b.id and a.tid>500000 limit 200;
2、如果A表的TID不是连续的,那么就需要使用覆盖索引.TID要么是主键,要么是辅助索引,B表ID也需要有索引。
select * from b , (select tid from a limit 50000,200) a where b.id = a .tid;
一条SQL加锁,可以分9种情况进行:
Multiversion (version) concurrency control (MCC or MVCC) 多版本并发控制 ,它是数据库管理系统一种常见的并发控制。
我们知道并发控制常用的是锁,当线程要对一个共享资源进行操作的时候,加锁是一种非常简单粗暴的方法(事务开始时给 DQL 加读锁,给 DML 加写锁),这种锁是一种 悲观 的实现方式,也就是说这会给其他事务造成堵塞,从而影响数据库性能。
我来解释一下 乐观锁 和 悲观锁 的概念。我觉得它俩主要是概念的理解。
很多人认为 MVCC 就是一种 乐观锁 的实现形式,而我认为 MVCC 只是一种 乐观 的实现形式,它是通过 一种 可见性算法 来实现数据库并发控制。
在讲 MVCC 的实现原理之前,我觉很有必要先去了解一下 MVCC 的两种读形式。
比如:
select * from table where xxx lock in share mode,
select * from table where xxx for update,
update table set....
insert into table (xxx,xxx) values (xxx,xxx)
delete from table where id = xxx
MVCC 使用了“三个隐藏字段”来实现版本并发控制,我查了很多资料,看到有很多博客上写的是通过 一个创建事务id字段和一个删除事务id字段 来控制实现的。但后来发现并不是很正确,我们先来看一看 MySQL 在建表的时候 innoDB 创建的真正的三个隐藏列吧。
RowID | DB_TRX_ID | DB_ROLL_PTR | id | name | password |
---|---|---|---|---|---|
自动创建的id | 事务id | 回滚指针 | id | name | password |
其实还有一个删除的flag字段,用来判断该行记录是否已经被删除。
而 MVCC 使用的是其中的 事务字段,回滚指针字段,是否删除字段。我们来看一下现在的表格(isDelete是我自己取的,按照官方说法是在一行开头的content里面,这里其实位置无所谓,你只要知道有就行了)。
isDelete | DB_TRX_ID | DB_ROLL_PTR | id | name | password |
---|---|---|---|---|---|
true/false | 事务id | 回滚指针 | id | name | password |
那么如何通过这三个字段来实现 MVCC 的 可见性算法 呢?
还差点东西! undoLog(回滚日志) 和 read-view(读视图)。
这时候,万事俱备,只欠东风了。下面我来介绍一下,最重要的 可见性算法。
其实主要思路就是:当生成read-view的时候如何去拿获取的 DB_TRX_ID 去和 read-view 中的三个属性(上面讲了)去作比较。我来说一下三个步骤,如果不是很理解可以参考着我后面的实践结合着去理解。
如果此条记录对于该事务不可见且 ROLL_PTR 不为空那么就会指向回滚指针的地址,通过undolog来查找可见的记录版本。
下面我画了一个可见性的算法的流程图
首先我创建了一个非常简单的表,只有id和name的学生表。
id | name |
---|---|
学生id | 学生姓名 |
这个时候我们将我们需要的隐藏列也标识出来,就变成了这样
isDelete | id | name | DB_TRX_ID | DB_ROLL_PTR | |
---|---|---|---|---|---|
是否被删除 | 学生id | 学生姓名 | 创建删除更新该记录的事务id | 回滚指针 |
这个时候插入三行数据,将表的数据变成下面这个样子。
isDelete | id | name | DB_TRX_ID | DB_ROLL_PTR |
---|---|---|---|---|
false | 1 | 小明 | 1 | null |
false | 2 | 小方 | 1 | null |
false | 3 | 小张 | 1 | null |
使用过 MySQL 的都知道,因为隔离性,事务 B 此时获取到的数据肯定是这样的。
id | name |
---|---|
1 | 小明 |
2 | 小方 |
3 | 小张 |
为什么事务A未提交的修改对于事务B是不可见的,MVCC 是如何做到的?我们用刚刚的可见性算法来实验一下。
首先事务A开启了事务(当然这不算开启,在RR模式下 真正获取read-view的是在进行第一次进行快照读的时候)。我们假设事务A的事务id为2,事务B的id为3。
然后事务A进行了更新操作,如图所示,更新操作创建了一个新的版本并且新版本的回滚指针指向了旧的版本(注意 undo log其实存放的是逻辑日志,这里为了方便我直接写成物理日志)。
最后 事务B 进行了快照读,注意,这是我们分析的重点。
首先,在进行快照读的时候我们会创建一个 read-view (忘记回去看一下那三个字段) 这个时候我们的 read-view 是:
up-limit-id = 2
alive-trx-list = [2,3]
low-limit-id = 4
然后我们获取那两个没有被修改的记录(没有顺序,这里为了一起解释方便), 我们获取到(2,小方)和(3,小张)这两条记录,发现他们两的 DB_TRX_ID = 1
, 我们先判断 DB_TRX_ID 是否小于 up-limit-id
或者等于当前事务id , 发现 1<2 小于 up-limit-id ,则可见 直接返回视图。
然后我们获取更改了的数据行:
其实你也发现了这是一个链表,此时链表头的 DB_TRX_ID 为 2,我们进行判断 2 < 2 不符合,进入下一步判断, 判断 DB_TRX_ID >= low_limit_id 发现此时是 2 >= 4 不符合 故再进入下一步,此时判断 Db_TRX_ID 是否在 alive_trx_list 活跃事务列表中,发现这个 DB_TRX_ID 在活跃列表中,所以只能说明该行记录还未提交,不可见。最终判断不可见之后通过回滚指针查看旧版本,发现此时 DB_TRX_ID 为1,故再次进行判断 DB_TRX_ID < up-limit-id ,此时 1 < 2 符合 ,所以可见并返回, 所以最终返回的是
id | name |
---|---|
1 | 小明 |
2 | 小方 |
3 | 小张 |
我们再来验证一下,这个时候我们将事务A提交,重新创建一个事务C并select。
我们预期的结果应该是这样的
id | name |
---|---|
1 | 小强 |
2 | 小方 |
3 | 小张 |
这个操作的流程图如下
这个时候我们再来分析一下 事务c产生的 read-view。
这个时候事务A已经提交,所以事务A不在活跃事务数组中,此时 read-view 的三个属性应该是
up-limit-id = 3
alive-trx-list = [3,4]
low-limit-id = 5
所以最终返回的就是
id | name |
---|---|
1 | 小强 |
2 | 小方 |
3 | 小张 |
为了加深理解,我们再使用一个相对来说比较复杂的示例来验证 可见性算法 。
首先我们在事务A中删除一条记录,这个时候就变成了下面的样子。
然后事务B进行了插入,这样就变成了下面这样。
然后事务B进行了 select 操作,我们可以发现 这个时候整张表其实会变成这样让这个 select 操作进行选取。
此时的 read-view 为
up-limit-id = 2
alive-trx-list = [2,3,4]
low-limit-id = 5
这个时候我们进行 快照读,首先对于前面两条小明和小方的记录是一样的,此时 DB_TX_ID 为 1,我们可以判断此时 DB_TX_ID = 1 < up-limit-id = 2 成立故返回。然后判断小张这条记录,首先也是 DB_TX_ID = 2 < up-limit-id = 2 不成立故进入下一轮,DB_TX_ID = 2 >= low-limit-id 不成立再进入最后一轮判断是否在活跃事务列表中,发现 DB_TX_ID = 2 在 alive-trx-list = [2,3,4] 中故不可见(如果可见则会知道前面的删除标志是已经删除,则返回的是空),则根据回滚指针找到上一个版本记录,此时 DB_TX_ID = 1 和上面一样可见则返回该行。
最后一个判断小亮这条记录,因为 DB_TX_ID = current_tx_id(当前事务id) 所以可见并返回。
这个时候返回的表则是这样的
id | name |
---|---|
1 | 小明 |
2 | 小方 |
3 | 小张 |
4 | 小亮 |
然后是事务A进行了select的操作,我们可以得知现在的 read-view 为
up-limit-id = 2
alive-trx-list = [2,3,4]
low-limit-id = 5
然后此时所见和上面也是一样的
这个时候我们进行 快照读,首先对于前面两条小明和小方的记录是一样的,此时 DB_TX_ID 为 1,我们可以判断此时 DB_TX_ID = 1 < up-limit-id = 2 成立故返回。然后判断小张这条记录,首先 DB_TX_ID = 2 = current_tx_id = 2 成立故返回发现前面的 isDelete 标志为true 则说明已被删除则返回空,对于第四条小亮的也是一样判断 DB_TX_ID = 4 < up-limit-id = 2 不成立进入下一步判断 DB_TX_ID = 4 >= low-limit-id = 5 不成立进入最后一步发现在活跃事务数组中故不可见且此条记录回滚指针为null所以返回空。
那么此时返回的列表应该就是这样了
id | name |
---|---|
1 | 小明 |
2 | 小方 |
虽然要分析很多,但多多益善嘛,多熟悉熟悉就能更深刻理解这个算法了。
之后是事务C进行 快照读 操作。首先此时视图还是这个样子
然后对于事务C的 read-view 为
up-limit-id = 2
alive-trx-list = [2,3,4]
low-limit-id = 5
小明和小方的两条记录和上面一样是可见的这里我就不重复分析了,然后对于小张这条记录 DB_TX_ID = 2 < up-limit-id = 2 || DB_TX_ID == curent_tx_id = 4 不成立故进入下一轮发现 DB_TX_ID >= low-limit-id = 5 更不成立故进入最后一轮发现 DB_TX_ID = 2 在活跃事务数组中故不可见,然后通过回滚指针判断 DB_TX_ID = 1 的小张记录发现可见并返回。最后的小亮也是如此 最后会发现 DB_TX_ID = 3 也在活跃事务数组中故不可见。
所以事务C select 的结果为
id | name |
---|---|
1 | 小明 |
2 | 小方 |
3 | 小张 |
后面事务A和事务B都进行了提交的动作,并且有一个事务D进行了快照读,此时视图还是如此
但此时的 read-view发生了变化
up-limit-id = 4
alive-trx-list = [4,5]
low-limit-id = 6
我们首先判断小明和小方的记录——可见(不解释了),小张的记录 DB_TX_ID = 2 < up-limit-id = 4 成立故可见,因为前面 isDelete 为 true 则说明删除了返回空,然后小亮的记录 DB_TX_ID = 3 < up-limit-id = 4 成立故可见则返回。所以这次的 select 结果应该是这样的
id | name |
---|---|
1 | 小明 |
2 | 小方 |
4 | 小亮 |
最后(真的最后了,不容易吧!),事务C有一次进行了 select 操作。因为在 RR 模式下 read-view 是在第一次快照读的时候确定的,所以此时 read-view是不会更改的,然后前面视图也没有进行更改,所以此时即使前面事务A 事务B已经进行了提交,对于这个时候的事务C的select结果是没有影响的。故结果应该为
id | name |
---|---|
1 | 小明 |
2 | 小方 |
3 | 小张 |
总结
我们来总结一下吧。
其实 MVCC 是通过 “三个” 隐藏字段 (事务id,回滚指针,删除标志) 加上undo log和可见性算法来实现的版本并发控制。
为了你再次深入理解这个算法,我再把这张图挂上来
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。