mysql索引B+树、MVCC、锁一文搞懂
1.innodb索引
innodb是页存储,一页是16K。
一个表的行数据都放到页里,单页都是单链表递增排序。
每个页之间都是双向链表保存。该页标记成数据页。
根据id查询时,也不知道在哪个数据页上。
因此会对数据页建立一个索引,每一个索引值的内容是数据页id,以及每一个数据页最小的id。通过二分就很容易定位到数据页。
当然,这个索引页也是16K,当数据足够多,也会进行索引页分类。
所以当索引页变多后,上面也会在建立一层索引页。
一页16*1024=16284字节
假设一个表的字段 8(bigint)+3(int)*3+8*2(timestamp)+3(utf8汉字)*20(长度)=93字节 已经很多大了。
平均一页有150条数据。
但是索引页的数据页号+索引最小值也就16字节。一页大概有1000条数据
那三层结构的树,就能支撑 150*1000*1000=1.5亿条数据。
所以一般三层的B+树就够了。也就进行三次磁盘IO,读取三次数据页。
//redo log的作用
mysql是先写日志在写数据。由于mysql使用内存buffer,并且一条sql修改的数据,会修改很多数据页,尤其是索引多的情况。并且这些数据页都是随机读取,数据很慢,因此mysql不会为很小的一次改动,就把这么多的数据页写到磁盘上。但是只写内存,服务器宕机会导致数据丢失,因此会记录redo log。这个log是磁盘顺序写,速度比较快。这就是redo日志的作用。
为什么使用B+树而不是B树
我们注意到,innodb的数据的索引模式,很像一颗树。一层层构建索引。
同时这种数据结构,数据也必须放到叶子节点上。这也是最契合需求的数据结构。
如果像B树一样,把数据放到节点上,那每一个页的能放的数据就很少,这样树的层数必然更深。从上面的分析中就可发现,150*1000*1000 随着表字段或者内容的增多,叶子页的能容纳的数据会越来越少,不过索引页里放的都是页号和索引值的最小值,这样必然索引的B+树不会超过4层就可满足数据要求。
同时B+的叶子节点之间是个链表,这样范围查找时,只需要对叶子节点进行遍历即可。
B树会把索引值和数据都在节点上,这样做范围查找时,必然要进行回溯或者多次查找。
索引
主键索引上有数据信息,因此称为聚簇索引。
其余页的索引,就是把内节点的主键最小值替换成索引列对应最小值即可。同时叶子节点存放的是索引列和主键ID的信息,因此需要再次回表查询。这种列索引称为二级索引或者辅助索引。
联合索引,就是一个内节点或者叶子节点上,有两个值。
这也解释了为啥遵循最左原则,毕竟联合索引的排序,是先比较第一个值,在比较第二个值。
2.mvcc
首先提下mysql的四种隔离级别
读未提交
读已提交
可重复读
串行
读未提交直接读取数据最新的数据就行(包括未提交的事务)。串行使用锁就行可。
所有mvcc也仅对读已提交和可重复读这两个隔离级别下的
事务中的update操作,会在聚簇索引和undo日志的 poll_pointer属性,构成一个记录的版本链。
ReadView
在上面两种隔离级别下,发生读操作时,会在不同时机生成ReadView。
其中的4个重要的内容
m_ids 生成ReadView时,活跃的(未提交的)事务id
min_trx_id 生成ReadView时,活跃的事务中,最小的事务id
max_trx_id 生成ReadView时,系统应该分配给下一个事务的id(事务id是连续递增的,注意,这里不是活跃id的最大值!!!)
creator_trx_id 生成该ReadView的事务id
有了ReadView后,在访问某条记录时,会遍历它的版本链,发现可以读到的数据。
1.如果被访问的trx_id跟ReadView的creator_trx_id一致的话,意味着当前事务访问它自己修改的记录,所以该版本可以被当前事务访问。
2.如果被访问版本的trx_id小于ReadView的min_trx_id,表示这个版本在生成ReadView之前已经提交,可以被访问。
3.如果被访问版本的trx_id大于max_trx_id,表示生成该版本的事务在当前事务ReadView生成之后开启,所以该版本也不被访问。
4.如果在min和max之间,并且在m_ids中,则不能被访问,反之说明该版本对应的事务已经结束,是可以访问的。
看完上面ReadView可见的规则后,一般都会有些迷茫的。尤其是4的规则。结合下面两种不同隔离级别下ReadView生成的时机,你就理解上面ReadView的设计了
读已提交级别
该级别规定只能读取已提交的,所以ReadView中m_ids表示正在活跃事务,也就是没有提交的事务,所以不能读取。哈哈,看到这,是不是对4理解了。活跃的事务不可读。
所以一次事务中多次读,每次都生成ReadView,检查对应的数据版本即可。
可重复读
该隔离级别要求 一次事务多同一条数据的读取,每次都得一样。因此每次都使用第一次读创建的ReadView即可。这样每次读的内容都一样了。
所以这里对ReadView是一种复用,这两种隔离级别的区别是ReadView的生成时机不一样。
二级索引与MVCC
前面说的都是聚簇索引的MVCC。
二级索引页的Page Header中有一个PAGE_MAX_TRX_ID的属性,当页面进行写操作时,发现页面的该值小于当前事务ID,直接将事务ID设置为页面的PAGE_MAX_TRX_ID。
如果读事务的ReadView的min_trx_id大于页面的最大事务id,那么该页数据都可被查看。如果小于,只需要进行回表之后,在进行可见性判断。
根据二级索引的主键id后,得到对应的聚簇索引记录后,在按照之前的逻辑进行查看。(还记得之前提到undo log里,如果没有修改的字段,log里没有,这里还需要对链表进行查询,找到该字段的最新值)
3.锁
锁模式
S锁 X锁,意向S锁,意向X锁
锁类型
LOCK_GAP
也就是间隙锁,它锁住的是某条记录之前的位置。
例如有两条主键为3和8的两条记录,3-8之间没有记录,当在8上加了间隙锁,那么其他事物,是不允许往3-8之间写数据了。
LOCK_REC_NOT_GAP单独加到一条记录上
LOCK_ORDINARY GAP和记录锁的合体,也是next-key锁
select for update是加X锁
表级锁 每个行级锁,都有对应IS,IX意向锁加到表上。
mysql加锁的方式太多,遇到死锁,可以根据死锁日志去看加锁的状态。
这里有几篇加锁case比较好的文章:https://mp.weixin.qq.com/s/Lavoo9sgulOzxQ22GRAamw 有兴趣的话,可以读一读
- 分享
- 举报
-
浏览量:5687次2021-08-10 14:04:57
-
浏览量:2961次2020-11-10 14:02:39
-
浏览量:2424次2019-11-18 09:23:04
-
浏览量:5055次2021-02-26 17:38:02
-
浏览量:462次2023-09-06 10:09:13
-
浏览量:11458次2020-12-09 20:55:40
-
浏览量:763次2023-04-17 16:07:31
-
浏览量:690次2023-09-08 10:47:07
-
浏览量:3473次2020-02-28 16:25:43
-
浏览量:2952次2023-07-11 11:25:40
-
浏览量:1839次2019-11-11 10:43:40
-
浏览量:2314次2020-08-03 16:35:15
-
浏览量:2875次2020-12-07 09:55:15
-
浏览量:4616次2021-02-25 14:03:23
-
浏览量:790次2022-09-16 11:54:38
-
浏览量:6803次2020-03-13 10:00:40
-
浏览量:3470次2019-11-19 08:57:44
-
浏览量:9289次2020-12-04 13:56:35
-
浏览量:48901次2019-07-26 15:43:22
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
JavaLetsGo
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明