《MYSQL实战45讲》笔记(1-10)

1:一条SQL查询语句是如何执行的?

下面我们来结合一张图来了解MySQL的基本架构
在这里插入图片描述
总体来看,MySQL分为服务层和存储引擎两个部分。其中存储引擎负责数据的存储和提取,而服务层负责连接的建立、分析、优化、执行等其他步骤。

常见的存储引擎
在这里插入图片描述
MySQL默认使用InnoDB作为存储引擎,常见的还有MyISAM、MEMORY等等。

在一条SQL语句的执行过程中,首先需要连接到数据库,

show processlist

在这里插入图片描述
command的状态为sleep代表当前有一个空闲连接。如果这个连接长时间没有命令,MySQL会断开连接,具体时间可以查看

show VARIABLES where variable_name = "wait_timeout"

在这里插入图片描述
长连接和短连接:
长连接可以减少短连接带来的资源重复创建销毁的消耗,但是会持续占用资源,当长连接过多的时候会导致内存问题。

一条语句可以分为以下几个部分:

  • 连接器:建立用户和MySQL服务的连接。
  • 分析器:服务层会将你输出的一条语句进行语法分析。
  • 优化器:根据SQL语句来选择具体的索引、表连接的顺序等等内容。
  • 执行器:首先会判断用户是否有表的具体权限,然后使用具体存储引擎提供的相关接口操作数据。

节后问题:如果表T中没有字段k,而你执行了这个语句 select *fromTwhere k=1, 那肯定是会报“不存在这个列”的错误: “Unknown column ‘k’ in ‘where clause’”。你觉得这个错误是在我们上面提到的哪个阶段报出来的呢?

我的猜测:首先排除连接器阶段,我认为分析器仅仅是分析语法应该不会去具体到表的字段,然后如果优化器也是可能不生效的,例如我执行全表扫描,所以应该是执行器阶段,真正执行语句的时候才发现表中没有这个字段。

答案:分析器。

2:日志系统:一条SQL更新语句是如何执行的?

更新语句涉及到两个重要的日志模块,redo log 和 binlog。

下面我们用记账的一个例子来深入理解一下这个过程,你是一个老板,你需要记录赊账的记录,如果有人来赊账或者还账你有两种做法

  • 将账本拿出来,把这次的账加上去或者扣除。
  • 先在一张纸上记下这次的账,然后等下班后在统计到账本上。

第一种做法仔细想想就会知道麻烦在哪里,首先你需要找到账本,其次你需要在密密麻麻的账本上找到具体的行数,然后再计算账本。如果账本比较小还可以接受,如果有300页、500页那么效率是否低的让人难以接受。

同样MySQL如果每一次更新操作直接写进磁盘整个io成本会变得十分高,所以MySQL通过写日志的方式,并在适当的时候将日志同步到磁盘。有了 redo log,InnoDB可以保证在数据库重启的时候之前保存的记录不会丢失。

如果说redo log是InnoDB特有的日志,那么binlog就是服务层的日志。

因为最开始的MySQL并没有InnoDB存储引擎。

这两种日志的区别:

  • redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
  • redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的
    是这个语句的原始逻辑。
    redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件
    写到一定大小后会切换到下一个,并不会覆盖以前的日志。

有了对这两个日志的概念性理解,我们再来看执行器和InnoDB引擎在执行这个简单的update语
句时的内部流程。

  1. 执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索找到这一行。如果ID=2这一
    行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然
    后再返回。
  2. 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在就是N+1,得到新的一行
    数据,再调用引擎接口写入这行新数据。
  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处
    于prepare状态。然后告知执行器执行完成了,随时可以提交事务。
  4. 执行器生成这个操作的binlog,并把binlog写入磁盘。
  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更
    新完成。
    这里我给出这个update语句的执行流程图,图中浅色框表示是在InnoDB内部执行的,深色框表
    示是在执行器中执行的。

两阶段提交:

当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数
据,那你可以这么做:
首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备
份恢复到临时库;
然后,从备份的时间点开始,将备份的binlog依次取出来,重放到中午误删表之前的那个时
刻。
这样你的临时库就跟误删之前的线上库一样了,然后你可以把表数据从临时库取出来,按需要恢
复到线上库去。
好了,说完了数据恢复过程,我们回来说说,为什么日志需要“两阶段提交”。这里不妨用反证法
来进行解释。
由于redo log和binlog是两个独立的逻辑,如果不用两阶段提交,要么就是先写完redo log再写
binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。
仍然用前面的update语句来做例子。假设当前ID=2的行,字段c的值是0,再假设执行update语
句过程中在写完第一个日志后,第二个日志还没有写完期间发生了crash,会出现什么情况呢?

  1. 先写先 redo log r 后写后 binlog b 。假设在redo log写完,binlog还没有写完的时候,MySQL进程异
    常重启。由于我们前面说过的,redo log写完之后,系统即使崩溃,仍然能够把数据恢复回
    来,所以恢复后这一行c的值是1。
    但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份
    日志的时候,存起来的binlog里面就没有这条语句。
    然后你会发现,如果需要用这个binlog来恢复临时库的话,由于这个语句的binlog丢失,这
    个临时库就会少了这一次更新,恢复出来的这一行c的值就是0,与原库的值不同。
  2. 先写先 binlog b 后写后 redo log r 。如果在binlog写完之后crash,由于redo log还没写,崩溃恢复以
    后这个事务无效,所以这一行c的值是0。但是binlog里面已经记录了“把c从0改成1”这个日
    志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是
    1,与原库的值不同
    可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的
    状态不一致。
    你可能会说,这个概率是不是很低,平时也没有什么动不动就需要恢复临时库的场景呀?
    其实不是的,不只是误操作后需要用这个过程来恢复数据。当你需要扩容的时候,也就是需要再
    多搭建一些备库来增加系统的读能力的时候,现在常见的做法也是用全量备份加上应用binlog来
    实现的,这个“不一致”就会导致你的线上出现主从数据库不一致的情况。
    简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保
    持逻辑上的一致

今天,我介绍了MySQL里面最重要的两个日志,即物理日志redo log和逻辑日志binlog。
redo log用于保证crash-safe能力。innodb_flush_log_at_trx_commit这个参数设置成1的时候,
表示每次事务的redo log都直接持久化到磁盘。这个参数我建议你设置成1,这样可以保证
MySQL异常重启之后数据不丢失。
sync_binlog这个参数设置成1的时候,表示每次事务的binlog都持久化到磁盘。这个参数我也建
议你设置成1,这样可以保证MySQL异常重启之后binlog不丢失。

我还跟你介绍了与MySQL日志系统密切相关的“两阶段提交”。两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案,

思考题:前面我说到定期全量备份的周期“取决于系统重要性,有的是一天一备,有的是一周一备”。那么在什么场景下,一天一备会比一周一备更有优势呢?或者说,它影响了这个数据库系统的哪个指标?

好处是“最长恢复时间”更短。

innodb_flush_log_at_trx_commit

show VARIABLES where variable_name = "innodb_flush_log_at_trx_commit"

在这里插入图片描述

sync_binlog

show VARIABLES where variable_name = "sync_binlog"

在这里插入图片描述

3: 事务隔离:为什么你改了我还看不见?

事务支持是在引擎层实现的

事务:一组操作全部成功或者全部失败;
MySQL原生的MyISAM引擎就不支持事务,InnoDB支持事务。
隔离级别跟效率是一个天平的两端。

在这里插入图片描述
其他数据默认隔离级别是读已提交 (read committed)

show variables like 'transaction_isolation'

在这里插入图片描述
可重复读的场景 :

假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。这时候你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。

在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从1被按顺序改成了2、3、4,在回滚日志里面就会有类似下面的记录
在这里插入图片描述
当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view。如图中看
到的,在视图A、B、C里面,这一个记录的值分别是1、2、4,同一条记录在系统中可以存在多
个版本,就是数据库的多版本并发控制(MVCC)。对于read-viewA,要得到1,就必须将当前
值依次执行图中所有的回滚操作得到。
同时你会发现,即使现在有另外一个事务正在将4改成5,这个事务跟read-viewA、B、C对应的
事务是不会冲突的。
你一定会问,回滚日志总不能一直保留吧,什么时候删除呢?答案是,在不需要的时候才删除。
也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。
什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的read-view的时候。
基于上面的说明,我们来讨论一下为什么建议你尽量不要使用长事务。

长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数
据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占
用存储空间。
在MySQL 5.5及以前的版本,回滚日志是跟数据字典一起放在ibdata文件里的,即使长事务最终
提交,回滚段被清理,文件也不会变小。我见过数据只有20GB,而回滚段有200GB的库。最终
只好为了清理回滚段,重建整个库。
除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库,这个我们会在后面讲锁的时候
展开。

SELECT
	* 
FROM
	information_schema.innodb_trx 
WHERE
	TIME_TO_SEC(
	timediff( now(), trx_started ))> 60

我会建议你总是使用set autocommit=1, 通过显式语句的方式来启动事务。

思考题:你现在知道了系统里面应该避免长事务,如果你是业务开发负责人同时也是数据库负责人,你会有什么方案来避免出现或者处理这种情况呢?

4:深入浅出索引(上)

索引的作用:提高数据查询的速度,相当于书的目录。

三种数据结构:是哈希表、有序数组和搜索树。下面是哈希索引示意图。

在这里插入图片描述
哈希的基本工作原理,通过hash函数计算值,如果相同则在相同位置拉一个链表依次向后排列。查找的时候先定位到具体的key,然后依次遍历链表。缺点:链表节点无序。

所以哈希表适合等值的场景,对于区间搜索则十分慢。
在这里插入图片描述
有序数组:一个数组会按照某个顺序依次排列,对于查询来说是最好的数据结构。缺点:插入成本高,每次插入一个元素为了保证有序都需要重新排列元素。

有序数组索引只适用于静态存储引擎。

在这里插入图片描述
如果单纯讨论搜索效率那么二叉树是最高的,实际上多数数据库都不使用二叉树,原因是索引不只是存放在内存中,还会存放在硬盘上。

二叉树的问题是树高会很大,导致会进行多次的磁盘IO,由于磁盘要远小于内存的速度所以综合下来二叉树反而性能不如多叉树。

为了让一个查询尽量少地读磁盘,就必须让查询过程访问尽量少的数据块。也就是减少树高的这个过程。

每一个索引在InnoDB里面对应一棵B+树。下面我们给出一个表结构。

create table T(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;

插入几条数据

INSERT INTO `t` VALUES (100, 1, 'r1');
INSERT INTO `t` VALUES (200, 2, 'r2');
INSERT INTO `t` VALUES (300, 3, 'r3');
INSERT INTO `t` VALUES (400, 4, 'r4');
INSERT INTO `t` VALUES (500, 5, 'r5');
INSERT INTO `t` VALUES (600, 6, 'r6');
INSERT INTO `t` VALUES (700, 7, 'r7');

数据库截图

在这里插入图片描述

在这里插入图片描述
根据叶子节点的内容,索引类型分为主键索引和非主键索引。

  • 主键索引的叶子节点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引(clustered index)。
  • 非主键索引的叶子节点内容是主键的值。在InnoDB里,非主键索引也被称为二级索引(secondary index)。

主键索引和普通索引的区别?

  • 如果where条件使用id进行搜索,则只需要搜索主键所在的B+树。
  • 如果where条件使用k进行搜索,则先需要搜索k所在的B+树获取到对应的id,然后搜索id索引树。
    这个过程就称为回表。

基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。

索引维护

B+树为了维护索引有序性,会对插入操作进行处理,包含页的合并和分裂。

哪些场景下应该使用自增主键

首先,主键自增可以保证新增数据都是对B+树的追加操作,也就是操作最后的数据块,不会挪动其他记录也不会触发叶子节点的分裂。

由于普通索引会存放主键索引,所以主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

有没有什么场景适合用业务字段直接做主键的呢?

只有一个索引、且必须是唯一索引这个时候就是kv的场景,将这个索引设置为主键,可以避免回表。

思考题:

如果你要重建索引 k,你的两个SQL语句可以这么写:

alter table T drop index k;
alter table T add index(k);

如果你要重建主键索引,也可以这么写:

alter table T drop primary key;
alter table T add primary key(id);

我的问题是,对于上面这两个重建索引的作法,说出你的理解。如果有不合适的,为什么,更好的方法是什么?

为什么重建索引:索引可能因为删除,或者页分裂等原因,导致数据页有空洞,重建索引的过程会创建一个新的索引,把数据按顺序插入,这样页面的利用率最高,也就是索引更紧凑、更省空间。

重建索引k的做法是合理的,可以达到省空间的目的。但是,重建主键的过程不合理。
不论是删除主键还是创建主键,都会将整个表重建。所以连着执行这两个语句的话,第一个语句就白做了。
这两个语句,你可以用这个语句代替 : alter table Tengine=InnoDB。

5:深入浅出索引(下)

问题:如果我执行下面的语句,需要执行几次树的搜索操作,会扫描多少行?

SELECT * FROM T WHERE k BETWEEN 3 AND 5

为什么会回表:由于查询结果所需要的数据只在主键索引上有,所以不得不回表。

索引覆盖:在一个查询中,索引可以直接提供查询的结果称为索引覆盖。例如通过普通索引获取主键id,通过主键索引获取其他数据。

问题:在一个市民信息表中,是否有必要将身份证和名字建立联合索引?

CREATE TABLE `tuser` (
`id` int(11) NOT NULL,
`id_card` varchar(32) DEFAULT NULL,
`name` varchar(32) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`ismale` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `id_card` (`id_card`),
KEY `name_age` (`name`,`age`)
) ENGINE=InnoDB

如果现在有一个高频请求,要根据市民的身份证号查询他的姓名,这个联合索引就有意义了。它可以在这个高频请求上用到覆盖索引,不再需要回表查整行记录,减少语句的执行时间。

最左前缀原则

在这里插入图片描述
当你要搜索第一个字是张的人的时候,你可以找到 id为3的数据,然后依次向后直到第一个字不是张。

在建立联合索引的时候如何安排顺序?

第一原则:如果可以通过调整顺序来少维护一个索引,优先考虑。

如果既有联合查询又有单独查询,那么单独查询的字段约小、占用空间越少越好。

索引下推

如果现在有一个需求:检索出表中“名字第一个字是张,而且年龄是10岁的所有男孩”。那么,SQL语句是这么写的:

select * from tuser where name like '张%' and age=10 and ismale=1;

5.6版本之前,MySQL只能从ID3开始一个个的回表进行筛选后续条件。
5.6引入索引下推优化,也就是如果联合索引后面字段包含age,会先比较age=10,如果不满足直接过滤。

思考题:

CREATE TABLE `geek` (
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
`c` int(11) NOT NULL,
`d` int(11) NOT NULL,
PRIMARY KEY (`a`,`b`),
KEY `c` (`c`),
KEY `ca` (`c`,`a`),
KEY `cb` (`c`,`b`)
) ENGINE=InnoDB;

公司的同事告诉他说,由于历史原因,这个表需要a、b做联合主键,为什么要创建“ca”“cb”这两个索引?
同事告诉他,是因为他们的业务里面有这样的两种语句:

select * from geek where c=N order by a limit 1;
select * from geek where c=N order by b limit 1;

这位同事的解释对吗,为了这两个查询模式,这两个索引是否都是必须的?为什么呢?

6:全局锁和表锁 :给表加个字段怎么有这么多阻碍?

根据加锁范围:MySQL可以大致分为全局锁、表锁、行锁。
全局锁:

Flush tables with read lock

适用场景:全库逻辑备份。存在问题:

  • 如果在主库执行命令,那么会导致业务停止。
  • 如果在从库执行命令,那么不能接受到主库binlog会导致主从延迟。

7: 行锁功过:怎么减少行锁对性能的影响?

MySQL的行锁是引擎自己实现的,比如MyISAM不支持行锁。

在下面的操作序列中,事务B的update语句执行时会是什么现象呢?假设字段id是表t的主键

在这里插入图片描述
两阶段锁协议:在 InnoDB事务中,行锁在需要的时候加上,在事务提交(COMMIT)后才释放。

  • 如果你的事务需要锁定多行,要将最有可能造成冲突的语句往后放。

假设这里有一个事务分为三个步骤:

  • 从顾客A账户余额扣除电影票价(update)。
  • 给影院B账户增加这个电影票价(update)。
  • 记录交易日志(insert)。

在这个事务中有两个update语句和一个insert语句,分析后得知最有可能造成冲突的语句是第二个操作。也就是将2语句放在事务的最后一步,这样可以最大限度的降低锁持有的时间,提高并发。

死锁和死锁检测

在这里插入图片描述
结合上面这张图,死锁的概念很好理解,两个事务分别持有对方的一部分行锁,导致都在等待。

处理方法:

  • 超时:innodb_lock_wait_timeout
SHOW VARIABLES 
WHERE
	Variable_name = 'innodb_lock_wait_timeout'

在这里插入图片描述

  • 发起死锁检测:innodb_deadlock_detect,主动回滚一个事务。
SHOW VARIABLES 
WHERE
	Variable_name = 'innodb_deadlock_detect'

在这里插入图片描述
超时的问题,MySQL默认时间是50s,如果将时间设置为1s后也会有有新的问题例如误伤正常的事务。
死锁检测的问题:每一个新来的线程都会进行死锁检测,会消耗大量的cpu资源,现象就是cpu利用率很高但是却执行不了几个事务。

怎么解决由这种热点行更新导致的性能问题呢

问题的症结在于,死锁检测需要消耗大量的cpu资源。

方法一:如果你可以确保业务不会出现死锁,可以将死锁检测关闭,关闭的风险,如果出现死锁必须等待默认50s的超时时间。
方法二:控制并发度,如果你可以控制一行并发度不会太高那么死锁检测的成本就会很低。但是如果每一个服务有10个并发,当集群规模上来以后还是会有峰值并发出现。

针对于方法二,并发控制应该继续向前移动,例如在redis或者gateway等地方做限流。如果有能修改MySQL源码的人,对于相同行的更新可以在进入引擎之前排队,可以避免大量的死锁检测。

设计优化(拆分):还是以影院账户为例,原先是1000个并发去访问一行,将一行记录拆分成10行,计算总额的时候将数据加起来,转账业务的时候随机选择其中一行处理,这样就降低了cpu消耗。

思考题

如果你要删除一个表里面的前10000行数据,有以下三种方法可以做到:

  • delete fromTlimit 10000;
  • 在一个连接中循环执行20次 delete fromTlimit 500;
  • 在20个连接中同时执行delete fromTlimit 500。

我的答案:
根据所学内容,删除操作是要持有行锁的,连接一多就会产生竞争,所以我排除第三个答案。在1和2中我选择2,原因我说不上来,但是1可能会有性能问题。

标准答案:

  • 1的问题:单个语句占用时间长,锁的时间也比较长;而且大事务还会导致主从延迟。
  • 3的问题:人为造成锁冲突。

8:事务到底是隔离的还是不隔离的?

一个在可重复读隔离级别下执行的事务,好像与世无争,不受外界影响。

一个事务要更新一行,如果刚好有另外一个事务拥有这一行的行锁,它又不能这么超然了,会被锁住,进入等待状态

问题是,既然进入了等待状态,那么等到这个事务自己获取到行锁要更新数据的时候,它读到的值又是什么呢?

例子

CREATE TABLE `t_0801` (
`id` int(11) NOT NULL,
`k` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into t_0801(id, k) values(1,1),(2,2);
start TRANSACTION with CONSISTENT SNAPSHOT

select k from t_0801 where id = 1 ;

commit;
start TRANSACTION with CONSISTENT SNAPSHOT

update t_0801 set k=k+1 where id = 1

select k from t_0801 where id = 1 ;

commit;
 
UPDATE t_0801 
SET k = k + 1 
WHERE
	id = 1

时序
在这里插入图片描述

结论:事务B查到的k的值是3,而事务A查到的k的值是1。

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作InnoDB表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用start transaction with consistent snapshot 这个命令。

在MySQL里,有两个“视图”的概念:

一个是view。它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是create view…,而它的查询方法与表一样。

另一个是InnoDB在实现MVCC时用到的一致性读视图,即consistent read view,用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。

可重复读隔离级别下,事务启动会拍一个基于整个库的快照,

InnoDB里面每个事务有一个唯一的事务ID,叫作transaction id。它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。

例如上面这个例子,ABC的事务id依次增大。

而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把transaction id赋值给这个数据版本的事务ID,记为rowtrx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。

也就是说,数据表中的一行记录,其实可能有多个版本(row),每个版本有自己的rowtrx_id。

在这里插入图片描述
图中虚线框里是同一行数据的4个版本,当前最新版本是V4,k的值是22,它是被transaction id为25的事务更新的,因此它的rowtrx_id也是25。

实际上,图2中的三个虚线箭头,就是undo log;而V1、V2、V3并不是物理上真实存在的,而是每次需要的时候根据当前版本和undo log计算出来的。比如,需要V2的时候,就是通过V4依次执行U3、U2算出来。

按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。

在实现上, InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务ID。“活跃”指的就是,启动了但还没提交。

这个视图数组把所有的rowtrx_id 分成了几种不同的情况。

在这里插入图片描述

这样,对于当前事务的启动瞬间来说,一个数据版本的rowtrx_id,有以下几种可能:

  1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是
    可见的;
  2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
  3. 如果落在黄色部分,那就包括两种情况
    a. 若 rowtrx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见;
    b. 若 rowtrx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见。

更新数据都是先读后写的,而这个读,只能读当前的值,称为当前读(current read)。

除了update语句外,select语句如果加锁,也是当前读。

select k from t_0801 where id = 1 lock in share mode;
select k from t_0801 where id = 1 for update

但是B事务没有commit,A事务会一直等待在这里。

9:普通索引和唯一索引,应该怎么选择?

场景: 假设你在维护一个市民系统,每个人都有一个唯一的身份证号,而且业务代码已经保证了不会写入两个重复的身份证号。如果市民系统需要按照身份证号查姓名,就会执行类似这样的SQL语句。

select name from CUser where id_card = 'xxxxxxxyyyyyyzzzzz';

当你给id_card加索引的时候,从性能的角度考虑,你选择唯一索引还是普通索引呢?选择的依据是什么呢?

我的答案:既然业务上保证了一致性,那么数据库层面可以考虑普通索引。

标准答案:

查询过程:

  • 普通索引来说,会一直查找到不符合条件停止,
  • 唯一索引只会查找到一个满足条件的就停止。

性能提升是微乎其微的。

InnoDB按页进行数据读取,将页加载到内存,默认为16kb。对于普通索引多做的一次指针寻找和计算。如果是页的最后的一个数据则会多加载一页,成本略高一点。对于整型字段,一页可以存放千个key。

更新过程:

当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InooDB会将这些更新操作缓存在change buffer中,这样就不需要从磁盘中读入这个数据页了在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

10:MySQL为什么有时候会选错索引?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值