一,索引 1.1 mysql 索引数据结构为什么要用B+树?
1.1.1 二叉树:这事要从二叉树说起,在搜索数据中,二叉树可是使复杂度从O(n),转化为O(logn),性能得到很大提升。不过存在树退化成链表的问题。
1.1.2 平衡二叉树:为解决以上的问题,出现了平衡二叉树(AVL),树的左右高度差不好过1,不会退化成链表。问题是旋转耗时,性能较低。尤其是删除节点时,AVL需要维护从跟节点到该节点路径上的所有节点的平衡,旋转的量级为O(logn)。
1.1.3 红黑树:红黑树只要求大致的平衡,只保证从根节点到叶子节点的最长路径不大于最短路径的两倍。这样在插入或删除节点的时候,只需要改变一个红黑节点节点,复杂度为O(logn);
尽管查询效率由于树的不严格平衡效率相比AVL有所降低,但是总体效率还是大大提升的。所以,红黑树用的更广法。
不过红黑树对于磁盘操作来说,高度还是太高了。树的高度决定了io的次数,也就制约了性能。
1.1.4 B树,对于磁盘相关的查找操作,就引入了B树,一种多路平衡查找树。因为每个节点可以有多路,从而使树高大大降低,从而减少io次数。
mongodb就使用的B树结构。
1.1.5 B+树是B树的一个变种。由于B树每个节点都存储数据,所以树的高度依然较高。
B+树节点不存数据,只记录索引。将所有data都存在叶子节点,从而使树高更低,更有效的减少io次数。
相比B树,有以下优点。
更少的IO次数;查询性能稳定,IO次数恒定;方便范围查询,叶子节点作为链表方便遍历。
1.1.6 那么为什么Mongodb使用B树,而mysql使用B+树就清楚了。
mysql 场景需要范围查询和多表关联,Mongodb一般不需要这样的场景,故而选择了适合自己场景的数据结构。
1.2 Innodb 的索引类型
1.2.1 从结构上来说,Innodb索引类型分为聚簇索引和非聚簇索引。
聚集索引的顺序就是数据的物理存储顺序,而非聚集索引的顺序和数据物理排列无关。
1.2.2 聚簇索引一定是主键吗?
聚集索引一般是表中的主键索引,如果表中没有显示指定主键,则会选择表中的第一个不允许为NULL的唯一索引,如果还是没有的话,就采用Innodb存储引擎为每行数据内置的6字节ROWID作为聚集索引。
1.2.3 非聚簇索引和聚簇索引的区别
非聚簇索引的叶子节点存的是索引和聚簇索引的id,而聚簇索引的叶子结点存的是记录本身。
所以非聚簇索引要先查到聚簇索引,再根据聚簇索引id查到记录,效率相对较低。
1.2.3 唯一索引与普通索引的区别
这个要说到change buffer。
由于对索引数据的增删改不仅要改变记录本身,还要操作索引文件。为了减少对索引文件的频繁io操作,Innodb实现了change buffer 。用来缓存对索引文件的操作。将一部分索引文件加到内存缓存起来,如果操作命中缓存,则将操作缓存起来,待系统空闲时再合并进行io请求,从而提高效率。
由于唯一索引增删时从要校验是否唯一,无法使用change buffer。从而使普通索引增删改的效率要比唯一索引高。
二,MVCC及事务隔离级别 多版本并发控制MVCC( Multi-Version Concurrency Control ),多个版本记录共存,实现读不加锁
2.1 mvcc的实现方式
Innodb,在每行数据后面添加两个隐藏值,分别是创建版本号和删除版本号。每开启一个新事物,版本号就会递增。
2.1.1.Insert时,将当前事务版本号填入创建版本号
2.1.2.Delete时,将当前事务版本号填入删除版本号。
2.1.3.Update时,插入一条新的记录,将当前事务版本号添加至新纪录的创建版本号。并将当前事务版本号添加到旧记录的删除版本号
2.1.4.那么在读时,如果是事务隔离级别为读可重复读
Select时,读取创建版本号《= 当前事务版本号,删除版本号为空,或》当前事务版本号。
如果事务隔离级别为读已提交
Select时,读取创建版本号为最近一次的版本号。
2.2 MVCC举例
举个简单的例子:
- 一个事务A(txnId=100)修改了数据X,使得X=1,并且commit了
- 另外一个事务B(txnId=101)开始尝试读取X,但是还X=1。但B没有提交。
- 第三个事务C(txnId=102)修改了数据X,使得X=2。并且提交了
- 事务B又一次读取了X。这时
- 如果事务B是Read Committed。那么就读取X的最新commit的版本,也就是X=2
- 如果事务B是Repeatable Read。那么读取的就是当前事务(txnId=101)之前X的最新版本,也就是X被txnId=100提交的版本,即X=1。
注意,这里B不论是Read Committed,还是Repeatable Read,都不会被锁,都能立刻拿到结果。这也就是MVCC存在的意义。
MVCC中,读取的可能是历史数据。也叫做快照读。在时效敏感的业务中,不适用。
在时效敏感的业务中,需要读取最新的数据,即当前读。
MVCC中(SELECT FROM)读到的数据是旧有的行,其他事务可能已经做了删除或修改。所以在本事务中的修改可能修改的是无效数据。从而是修改无效。
所以需要使用当前读,select from lock in share mode/for update
update ,insert ,delete 使用的是当前读
三,Innodb中的锁机制 3.1.为什么要加锁?锁机制用于解决对共享资源的并发访问。如并发问题,丢失更新。
3.2.Innodb 锁从颗粒度角度来说,可以分为行锁和表锁。行锁是针对于索引加的锁,而非记录。所以只有通过索引检索数据,才使用行锁。否则使用表锁。
行锁效率高,会出现死锁。表锁相反。
3.3什么是乐观锁和悲观锁?
乐观锁和悲观锁是两种锁的设计理念。乐观锁假定大概率不会发生更新冲突,所以在数据访问处理的过程中不加锁,只有更新是才检测版本号或时间戳是否存在冲突。
悲观锁假定大概率会发生更新冲突,在访问数据,处理数据之前就加排他锁,整个处理过程锁定数据,只有事务提交或回滚才释放锁。
悲观锁的实现,比如写锁,即排他锁。
乐观锁的是实现,依靠表设计和代码来实现,比如在商品表内添加version版本字段或时间戳字段。
3.4 Innodb有三种行锁算法
记录锁:record Lock,锁定一个行记录;间隙锁:Gap Lock,锁定一个区间;记录锁+间隙锁:Next-Key Lock 锁定行记录+区间;
3.4.1 记录锁就是锁住一行记录;
3.4.2. 间隙锁只有在事务隔离级别 RR 中才会产生;
3.4.3. 唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,指定给某条存在的记录加锁的时候,只会加记录锁,不会产生间隙锁;
3.4.4. 普通索引不管是锁住单条,还是多条记录,都会产生间隙锁;
3.4.5. 间隙锁会封锁该条记录相邻两个键之间的空白区域,防止其它事务在这个区域内插入、修改、删除数据,这是为了防止出现 幻读 现象;
3.4.6. 普通索引的间隙,优先以普通索引排序,然后再根据主键索引排序
3.5 幻读的问题
Innodb是怎么在可重复读隔离级别下解决幻行的?
间隙锁。
四,redo log/binlog/undo log 4.1,redo log
实现方式:Innodb存储引擎
数据库并不是每次操作都写到磁盘(随机IO),而是先将操作写到内存,过一段时间,再将所有操作一起写入磁盘(顺序IO),这样可以减少IO操作。
redo log就提供这样的功能,每句sql都写到redo log 的内存里,并标记为prepare。当事务提交后,会将此条redolog置为commit状态。之后再进行写入磁盘。
这样就减少了IO操作,并保证一旦落库,即使数据库宕机,也能从redolog恢复已提交的事务。
redolog缓存是固定大小的循环缓存.
redolog 可以保证事务的持久性。当发生故障时,如果有数据没有写入磁盘,在重启数据库时,可以根据redolog重做数据,从而保证事务的持久性。
4.2,binlog
实现方式:数据库server层
binlog 是对数据更新进行的记录,以事务的形式,保存在磁盘中。
4.2.1主要作用:
1.主从复制
2.数据恢复
3.增量备份
4.2.2binlog有三种模式:
1.statement:保存sql语句
2.row:每行被修改成了何种数据。
3.mixedLevel:混合使用,自动判断
4.3,undolog
【mysql|一文深入理解mysql】实现方式:Innodb存储引擎,是逻辑表,即在数据记录中增加列和版本字段。
作用:
1.实现数据回滚
2.实现MVCC
4.4,redolog 和binlog 更新顺序
两个日志记录的顺序:
更新的行如果不在内存,从磁盘取出 -> 修改内存中的值 -> 写入redo-log状态为prepare -> 写binlog -> 提交事务redo-log进行commit
4.5 innodb_flush_log_at_trx_commit和sync_binlog
Log Buffer(日志缓冲区)是一块内存区域用来保存要写入磁盘上的日子文件的数据。主要包括InnoDB存储引擎层日志:redo日志和undo日志。
innodb_flush_log_at_trx_commit参数:
0: 由mysql的main_thread每秒将存储引擎log buffer中的redo日志写入到log file,并调用文件系统的sync操作,将日志刷新到磁盘。
1:每次事务提交时,将存储引擎log buffer中的redo日志写入到log file,并调用文件系统的sync操作,将日志刷新到磁盘。
2:每次事务提交时,将存储引擎log buffer中的redo日志写入到log file,并由存储引擎的main_thread 每秒将日志刷新到磁盘。
sync_binlog参数:
0 :存储引擎不进行binlog的刷新到磁盘,而由操作系统的文件系统控制缓存刷新。
1:每提交一次事务,存储引擎调用文件系统的sync操作进行一次缓存的刷新,这种方式最安全,但性能较低。
n:当提交的日志组=n时,存储引擎调用文件系统的sync操作进行一次缓存的刷新。
以上
推荐阅读
- mysql|InnoDB数据页结构
- 数据库|SQL行转列方式优化查询性能实践
- javaweb|基于Servlet+jsp+mysql开发javaWeb学生成绩管理系统
- 达梦数据库|DM8表空间备份恢复
- 数据技术|一文了解Gauss数据库(开发历程、OLTP&OLAP特点、行式&列式存储,及与Oracle和AWS对比)
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- 谈灾难恢复指标(RTO与RPO是什么鬼())
- RPO与RTO