关于Mysql相关原理的理解

目录

1 InnoDB引擎

1.1 索引

1.2 事务

1.3 事务隔离级别

1.4 MVCC原理

2 回表

3 覆盖索引

4 最左匹配原则

5 主键生成问题

6 Mysql的行锁和表锁


1 InnoDB引擎

1.1 索引

使用索引加快查询速度,其实就是将无序的数据变成有序,在InnoDB引擎中索引的底层数据结构是B+树。

那么为什么不适用红黑树和B树呢?

Mysql的数据时存储在硬盘的,在查询时一般不能一次性把全部数据加载到内存中。

红黑树是二叉查找树的变种,一个Node节点只能存储一个Key和一个Value。

B和B+树与红黑树不一样,它们算是多路搜索树,相较于二叉搜索树而言,多路搜索树的高度会较低。所以很容易发现,在数据不能一次加载至内存的场景下,数据需要被检索出来,选择B或B+树的理由就很充分了(一个Node节点存储信息更多),树的高度更低,而这会影响检索的速度。

B+树相对于B树而言,有两个特性:

  • B+树非叶子节点不存储数据,在相同的数据量下,B+树更加矮壮。(数据都存储在叶子结点,非叶子结点能存储更多的索引)
  • B+树叶子结点之间组成一个链表,方便于遍历查询。(遍历操作在Mysql中比较常见)

在Mysql InnoDB引擎下,每创建一个索引,相当于生成了一颗B+树,如果这个索引是聚集索引,那当前B+树的叶子节点存储着主键和当前行的数据;如果索引是非聚集索引,那当前B+树的叶子结点存储着主键和当前索引散列值。

比如:select * from user where id>=10;

执行时只要定位到id为10的记录,然后在叶子节点之间通过遍历链表(叶子节点组成的链表),就可以找到往后的记录了。

而由于B树是会在非叶子结点也存储数据,要遍历的时候可能得跨层检索,相对麻烦些,所以基于树的层级以及业务使用场景特性,Mysql选择B+树作为索引的底层数据结构

其实InnoDB引擎是自适应哈希索引的,哈希索引的创建由InnoDB存储引擎自动优化创建无法干预。

1.2 事务

事务可以使一组操作全成功或者全失败,事务目的是为了保证数据最终的一致性。

其几大特性主要分为原子性、一致性、隔离性、持久性。

  • 原子性:当前事务的操作同时成功或同时失败,由undo log保证,因为undo log记载着数据修改前的信息,比如insert了一条数据,那么undo log会记录一条对应的delete日志,updat记录时会记录之前的原始值。
  • 隔离性:在事务并发时内部的操作不会相互干扰,如果多个事务可以在同时刻操作同一份数据那么就可能会产生脏读、重复读、幻读的问题。于是事务与事务之间需要一定的隔离,在InnoDB引擎中定义了四种隔离级别分别是read uncommit、read commit、repeatable commit、serializable,事务隔离级别越高隔离性越好但性能越低。
  • 持久性:一旦提交了事务,它对数据库的改变就是永久性的。由redo log日志保证,当我们修改数据时,先把这条记录所在的页找到然后把该页加载到内存,将记录修改,为了防止内存修改完了Mysql就挂掉了,引入了redo log。redo log是顺序写的,写入速度很快,并且它记录的是屋里修改,文件体积很小,恢复速度也很快。
  • 一致性:可以理解为我们使用事务的目的,而隔离性、原子性、持久性均是为了保障一致性的手段,比如事务过程中出现异常需要回滚事务而不是强行提交事务导致数据问题。

1.3 事务隔离级别

1) read uncommit。

简单来说就是事务B读取了事务A还没提交的数据,也就是脏读。

  • 对于锁维度而言,其实就是read uncommit隔离级别下,读不会加任何锁,而写会加排它锁。
  • 对于更新操作而言,InnoDB是肯定会加写锁的(数据库是不可能允许在同一时间更新同一条数据的),而读操作如果不加任何锁,就会导致上面说的脏读。

事实上如果读加锁的话,更新数据时就无法取数据了,极大地影响数据库性能。

所以在Mysql InnoDB引擎层面,有了新的解决方案Multi-version concurrency control。在MVCC下就可以做到读写不阻塞,且避免了类似脏读的问题。

MVCC通过生产数据快照并用这个快照来提供一定级别(语句级或事务级)的一致性读取。回到事务隔离级别下,针对于read commit隔离级别,它生成的就是语句级快照,而针对repeatable read,它生成的就是事务级的快照。

read uncommit解决脏读的思路,就是在读取的时候生成一个版本号,等到其他事务commit了之后才会读取最新已commit的版本号数据。比如事务A读取了记录生成了版本号,事务B修改了记录,事务A再读取的时候,是根据最新的版本号来读取的,如果事务B还没commit,那么事务A还是读取之前版本号的数据。

2) read commit。

read commit解决了脏读但也会有不可重复读的并发问题。

比如A查询数据时,由于B修改了数据库导致A每次都查询到不同的结果。

3) repeatable read。

repeatable read隔离级别是事务级别的快照,每次读取的都是当前事务的版本,即使当前数据被其他事物修改了,也只会读取当前版本的数据。

所以在InnoDB引擎下的repeatable read隔离级别下,在MVCC下已经解决了幻读的问题,因为它读取的是历史版本的数据。而如果是当前读则需要配合间隙锁来解决幻读的问题。

4) serializable。

serializable隔离级别,是最高的隔离级别,相当于不允许事务并发,效率最低但是最安全嘚

1.4 MVCC原理

主要是通过read view和undo log来实现的。

  • undo log会记录修改数据前的信息,事务中的原子性就是通过undo log实现的,所以undo log可以帮我们找到其他版本数据。
  • read view实际上就是在查询时InnoDB会生成一个read view,包含几个重要字段:trx_ids(尚未提交事务版本号的集合)、low_limit_id(下一次要生产的事务ID值)、creator_trx_id(当前事务版本号),在每行数据有两列隐藏的字段,分别是DB_TRX_ID(当前事务ID)和DB_ROLL_PTR(指向上一个版本数据在undo log里面的位置指针)。

简单来说就是比对版本来实现读写不阻塞,而版本的数据存在于undo log中,而针对于不同的隔离级别,无非就是read commit隔离级别下,每次获取一个新的read view、repeatable read隔离级别则每次事务只获取一个read view。

2 回表

其实所谓的回表,就是当我们使用非聚集索引查询数据时,检索的数据可能包含其它列,但走的索引树叶子节点只能查到当前列值以及主键ID,所以需要根据主键ID再去去查一遍数据,得到SQL所需的列。

select orderId, orderName from orderDetail where orderId=123

比如orderId是索引,索引树的叶子结点只有orderId和Id,而我们还想检索出orderName,所以Mysql会用Id再去查出orderName返回给我们,这过程就叫做回表。

3 覆盖索引

所以想要避免回表,需要使用覆盖索引。

实际上就是将想要查询的列刚好在叶子结点上都存在,比如创建了orderId和orderName联合索引,刚好需要查询的也是这两个字段,这些数据都存在索引树的叶子节点上就不需要回表操作了。

4 最左匹配原则

简单来说,有个索引(a, b, c, d),查询条件a=1 and b=2 and c>3 and d=4,那么每个节点依次命中a, b, c 而没有d,因为先匹配最左边的,索引只能用于查询key是否相等,遇到范围查询(>, <, like, between)就会退化为线性查找了。

5 主键生成问题

首先主键要保证唯一性和空间尽可能短,而由于索引的特性(有序),如果生成像uuid那种主键,插入的性能会比自增的差,因为uuid在插入时可能需要移动磁盘块,比如块内存的空间在当前时刻已经存储满了但新生成的uuid需要插入已满的块内,就需要移动块的数据。

6 Mysql的行锁和表锁

行锁实际上作用于索引上,当我们SQL命中了索引,那锁住的就是命中条件内的索引节点(行锁),如果没命中索引,那锁的就是整个索引树(表锁)。

行锁又可以简单分为读锁(共享锁和死锁)和写锁(排它锁)

  • 读锁是共享的,多个事务可以同时读取同一个资源,但不允许其他事物修改。
  • 写锁是排他的,写锁会阻塞其他的写锁和读锁。

你可能感兴趣的:(Java,#,Mysql,软件/工具,mysql,数据库,java)