MySql
MySql索引
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用 B 树及其变种 B+ 树。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
1 | 为表设置索引要付出代价的: |
查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在 O(log2n)的复杂度内获取到相应数据。
1 | 创建索引的好处: |
1 | 创建索引的坏处: |
Hash索引
1 | 1.基于哈希表的实现,只有精确匹配索引所有列的查询才有效 |
聚簇索引
聚簇索引也叫聚集索引,该索引中键值的逻辑顺序决定了表中相应行的物理顺序。
聚集索引确定表中数据的物理顺序。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引,通常是主键。
不是单独的索引类型,而是一种数据存储方式,指的是数据行跟相邻的键值紧凑的存储在一起。
非聚集索引
非聚集索引也称作辅助索引,该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。
索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。
聚集索引与辅助索引相同的是:不管是聚集索引还是辅助索引,其内部都是B+树的形式,即高度是平衡的,叶子结点存放着所有的数据。
聚集索引与辅助索引不同的是:叶子结点存放的是否是一整行的信息。
数据文件跟索引文件分开存放。
1 | 聚集索引 |
唯一索引
唯一索引是不允许其中任何两行具有相同索引值的索引。
当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。
主键索引
1 | 1.主键不允许空值,唯一索引允许空值 |

为什么建议使用主键自增的索引?

1 | 如果插入的是 ID = 350 的一行数据,由于 B+ 树是有序的,那么需要将下面的叶子节点进行移动,腾出位置来插入 ID = 350 的数据,这样就会比较消耗时间。 |
单列索引
即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引;
当一个表中查询大的情况下,where条件中有多个,如果使用多个单列索引,根据mysql优化器策略,造成可能只使用一个索引,其他索引会失效,导致会全盘扫描表。
组合索引
即一个索引包含多个列。
当一个表中查询大的情况下,where条件中有多个,那么可以使用组合查询,不会扫描表,直接从索引中获取,查询效率高。
1 | 场景: |
覆盖索引
1 | 1、如果一个索引包含所有需要查询的字段的值,我们称之为覆盖索引 |
局部性原理和磁盘预读
由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘 I/O。
为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。
局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。
由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高 I/O 效率。
预读的长度一般为页(4K)(page)的整倍数。
页分裂
页可能填充至100%,在页填满了之后,下一页会继续接管新的记录。但如果下一页也满了,数据又需要按顺序插入,就会产生页分裂。
1 | InnoDB的做法是(简化版): |
等于创建了一个新页,并且把当前页的数据迁移部分到新页中,然后插入在当前页和下一页的中间。
页合并
当你删了一行记录时,实际上记录并没有被物理删除,记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。

当页中删除的记录达到MERGE_THRESHOLD(默认页体积的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。
B树演变成B+树的过程和原因



1 | B+树的根节点会被存储在内存中,其他节点存储在磁盘中 |
Order By
1 | 双路排序: |
1 | 单路排序: |
当需要排序的列的总大小超过max_length_for_sort_data定义的字节,mysql会选择双次排序,反之使用单次排序,当然,用户可以设置此参数的值来选择排序的方式.
子查询
1 | 1.当Limit的值过大,尽量使用子查询,能够提高效率 |
索引优化
索引下推
索引下推(index condition pushdown )简称ICP,在Mysql5.6的版本上推出,用于优化查询。
在不使用ICP的情况下,在使用非主键索引(又叫普通索引或者二级索引)进行查询时,存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件 。
在使用ICP的情况下,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器 。
索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。
Mysql存储引擎
1 | innodb存储引擎: |
ACID中,最重要的是一致性,其中A的原子性是通过undolog来保证的,隔离性是通过锁机制(MVCC、间隙锁)实现的,持久性是通过redolog来实现的,一致性是通过原子性、隔离性、持久性来实现的。
redolog


第二种最安全,第三种效率最高。
undolog

binlog


MyISAM表锁
1 | CREATE TABLE `mylock` ( |
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
表共享读锁
一个session使用lock table给表加读锁,这个session可以锁定表中的记录,但更新和访问其他表都会提示错误,同时,另一个session可以查询表中的记录,但更新就会出现锁等待。
1 | #获得表的read锁定 |
1 | #获取表的read local(session)锁定 |
表独占写锁
当一个线程获得对一个表的写锁之后,只有持有锁的线程可以对表进行更新操作。其他线程的读写操作都会等待,直到锁释放为止。
1 | #获取表的write锁定 |
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺:
1 | mysql> show status like 'table%'; |
MyISAM在执行查询语句之前,会自动给涉及的所有表加读锁,在执行更新操作前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要使用命令来显式加锁,上例中的加锁时为了演示效果。
InnoDB锁
InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!
在不通过索引条件查询的时候,innodb使用的是表锁而不是行锁。
行共享锁
共享锁又称读锁。允许多个事务对统一数据可以共享一把锁,都能访问到数据库,但是只能读不能修改,如果有其他事务进行修改操作,会被阻塞直到共享锁释放才修改成功。
1 | #关闭事务自动提交 |
行排他锁
排他锁又称写锁。排它锁不能与其他锁并存,如果一个事务获取了一个数据行的排它锁,其他事务就不能在获取该行的锁,只有当前获取了排它锁的事务可以对数据进行读取和修改。
1 | #关闭事务自动提交 |
可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:
1 | mysql> show status like 'innodb_row_lock%'; |
自增锁
针对自增列自增长的一个特殊的表级别锁。
1 | #默认值1,代表连续,事务未提交则id永远丢失,会接着新的id递增 |
MVCC
脏读:事务能够读取未提交的数据。
不可重复读:当一个事务在执行过程中,数据被另外一个事务修改,造成本次事务前后读取的信息不一样。
幻读:当一个事务A读取某一个范围的数据时,另一个事务B在这个范围插入行,A事务再次读取这个范围的数据时,会产生幻读。
事务隔离级别的原理:MVCC + 锁
当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
关系
MVCC多版本并发控制指的是维持一个数据的多个版本,使得读写操作没有冲突,快照读是MySQL为实现MVCC的一个非阻塞读功能。MVCC模块在MySQL中的具体实现是由三个隐式字段,undo日志、read view三个组件来实现的。
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
解决脏读、幻读、不可重复读等事务隔离问题。
隐藏字段
DB_TRX_ID:6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id
DB_ROLL_PTR:7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog,指向上一个旧版本
DB_ROW_JD:6字节,隐藏的主键,如果数据表没有主键,那么innodb会自动生成一个6字节的row_id
undolog
MVCC的快照版本数据就是存储在undolog中。

不同事务或者相同事务的对同一记录的修改,会导致该记录的undolog生成一条记录版本线性表,即链表,undolog的链首就是最新的旧记录,链尾就是最早的旧记录。
Read View
Read View是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的。
1 | 读已提交原理: |
如果事务中都是用快照读,那么不会产生幻读的问题,但是快照读和当前读一起使用的时候就会产生幻读。
通过使用next-key解决(select * from user where age = 20 for update)。
最后更新: 2021年03月16日 17:05