前言
最近在看小林的图解MySQL,就顺便记录一下,这里只记录一些我觉得比较难懂的地方(毕竟做前端的),更详细的建议去看小林的图解MySQL
一、行数据是如何在磁盘中存储的?
先上图,这里面的一些标注不懂的可以看小林的这篇文章
注意:变长字段长度列表(蓝色区域)存放的是varchat类型的name、phone,
03 01分别代表name、phone的真实所占的字节数,但存放的顺序是逆序的
逆序存放的主要原因
前提条件,不懂Cup Cache Line的可以看小林的这篇文章
了解上面的内容之后,说结论:逆序存放的原因 使得位置靠前的字段长度信息和其真实数据可以同时在一个 CPU Cache Line 中,这样就可以提高 CPU Cache 的命中率,也就是位置靠前的字段更接近变长字段长度列表,更可能位于同一个 Cache Line 中,(因为表中位置靠前的字段一般比较重要),变长字段长度列表(蓝色区域)中还有一个记录null的列表,也是逆序存放,原因同上。(如果一张表中没有varchat类型和null类型的字段,那这个记录列表就不会生成)
二、索引篇
具体可以看小林的这篇文章
阅读下面内容需要了解在MySQL InnoDB 选择 B+tree 作为索引的数据结构,最左匹配原则,这都属于八股文,不再赘述
在使用联合索引时要注意的点,以及优化方法
// 创建一个联合索引
CREATE INDEX index_product_no_name ON product(a, b);
Q1:select * from t_table where a > 1 and b = 2
- 只有a用到了索引,扫描的区间是(1,+∞),与b没有关系,因为在a>1的范围内,b是无序的
Q2:select * from t_table where a >= 1 and b = 2
- a,b都用到了索引,扫描的区间是[1,+∞),与Q1不一样的是,这里的a字段的查询条件是>=,在a=1的查询条件下,b是有序的,索引扫描可以先找到第一个满足a=1 AND b=2的数据,然后又因为B+tree的叶子结点是双向链表的结构,可以沿着链表向后搜索,直到某条记录不满足a>=1的条件
Q3: select * from t_table where a between 2 and 8 and b = 2
- MySQL中between包含左右区间,用上面查询语句举例子就是:[2,8]
- 这个同Q2,区别在于搜索的边界条件是[2,8],
索引优化的具体方法查看索引优化
为什么要使用B+tree?
- MySQL为了存储数据持久化,把数据存在了磁盘上,磁盘读写的最小单位是扇区,扇区的大小只有
512B大小,操作系统一次会读写多个扇区,所以操作系统的最小读写单位是块(Block)。Linux 中的块大小为4KB,也就是一次磁盘 I/O 操作会直接读写 8 个扇区。 - 索引是存在磁盘上的,读取数据的时候要先把索引读取到内存中,根据索引找到数据在磁盘中的物理位置,然后再读取到内存中
- 这样做减少了磁盘的I/O
------- 1.6 -------
三、锁篇
原文地址锁篇(ps:下面例子均处于MySQL默认的隔离级别(不可重复度))
- 表锁:顾名思义就是锁住整张表
- 行锁:可以锁住某一行也可以锁住某个区间,也可分为共享锁和独占锁(共享锁(S锁,读锁)满足读读共享,读写互斥。独占锁(X锁,写锁)满足写写互斥、读写互斥)
举个例子:
time1:update user set age=18 where id<20 AND id>1;(事务A)
time2:insert into user (id,xxx) values (10,xxx);(事务B)
事务A会生成一个间隙锁锁住(1,20)这个区间,事务B想在这个区间插入一条数据就会被阻塞等待事务A提交,事务B会生成一个插入意向锁.
总结:间隙锁的意义只在于阻止区间被插入,因此是可以共存的。一个事务获取的间隙锁不会阻止另一个事务获取同一个间隙范围的间隙锁,共享(S型)和独占(X型)的间隙锁是没有区别的,他们相互不冲突,且功能相同。(ps:time2时刻执行inser会被阻塞,delete也会被阻塞)
还是这个例子,升级一下:
time1:update user set age=18 where id<20 AND id>1;(事务A)
time3: insert into user (id,xxx) vlause (11,xxx);
time2: update user set age=18 where id<20 AND id>1;(事务B)
time4:insert into user (id,xxx) values (10,xxx);
事务A生成一个间隙锁(1,20),事务B生成一个间隙锁(1,20)(ps:间隙锁之间不会相互冲突),time3时刻事务A因为事务B的间隙锁而被阻塞,生成一个插入意向锁,同理time4也是,此时就形成了死锁,(ps: 互斥,占有且等待,不可剥夺,循环等待)
锁相关的就先记录到这里,还有很多没有介绍到的锁可以去小林的网站上看
四、事务篇
原文地址事务篇
MySQL的InnoDB引擎默认隔离级别是
不可重复读(ps:是InnoDB引擎,而不是MySQL),在这种级别下会出现幻读,MySQL的MVCC机制很大程度上可以解决幻读的情况.
为什么?
在一个事务开始后(begin/start transaction),会生成一个Read View (可以理解为生成了一个快照),接下来在这个事务内的所有读操作都会基于这个Read View
那这个Read View是什么?
一张表中的每一行数据除了我们能看到的源数据之外MySQL还添加了很多隐藏的列,比如:
trx_id这一列是用来记录修改这行数据的事务id.
有了以上这些知识,就可以解释为什么Read View可以很大程度上避免幻读了.
比如我现在有一个事务A,事务id是49,但现在数据库中的这行数据被事务id为50的事务修改了(比如修改了name 小明-->小林),在事务A中查询这条记录,事务A会取出自己的Read View中creator_trx_id跟此时表中的trx_id列进行对比,如果creator_trx_id<trx_id,不会使用,然后从roll_pointer链中向下查找,向下查询一层就到了trx_id=49这条记录,可以使用.
以上情况被称为快照读,是在执行普通的select查询时发生的
还有一种当前读也可以有效避免幻读,在执行select .. for update这种查询时触发
通过Next-Key Lock(记录锁+间隙锁)解决,锁篇介绍过间隙锁,这里简单介绍一下记录锁,两者很像,区别在于间隙锁锁住的是一个区间,记录锁锁住的是特定的一行.
如果有其他事务在 Next-Key Lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
------- 2.8 -------