极客时间-MySQL实战45讲学习笔记

01|基础架构:一条sql查询语句是如何执行的

mysql能够分为sever层和存储引擎层两层

02|日志系统:一条sql更新语句是如何执行的

一条更新语句如:update T set c = c + 1 where ID = 2;和查询的流程差很少,可是更新流程还设计到两个重要的日志模块:分别是redo log(重作日志)和binlog(归档日志).mysql

redo log

当有一条记录须要更新的时候,InnoDB引擎就会把记录写到redo log(粉板)里面,并更新内存,InnoDB引擎会在适当的时候,将这个操做记录更新到磁盘里面,而这个更新每每是在系统比较闲的时候作,这就像打烊之后的掌柜作的事,sql

InnoDB的redo log是固定大小的,好比能够配置一组4个文件,每一个文件的大小是1GB,那么这个“粉板”总共就能够记录4GB的操做,从头开始写,写到末尾就又回到开头循环写,以下图:数据库

有了redo log,InnoDB就能够保证即便数据库发生异常重启,以前提交的记录都不会丢失,这个能力称之为crash-safe;数组

##binlog redo log是引擎特有的日志,server层也有本身的日志,称为binlog(归档日志)安全

这两种日志的不一样点:性能优化

  • redo log是InnoDB引擎特有的;binlog是mysql的server层实现的,全部引擎均可以使用;
  • redo log是物理日志,记录的是“某个数据页上作了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,好比“给ID=2这一行的c字段加1”;
  • redo log是循环写的,空间固定会用完;binlog是能够追加写入的.“追加写”是指binlog文件写到必定大小后会切换到下一个,并不会覆盖之前的日志.

update语句的内部流程:bash

  • 一、执行器先找到引擎取ID=2这一行,ID是主键,引擎直接用树索引找到这一行,若是ID=2这一行所在的数据页原本就在内存中,就直接返回给执行器;不然,须要先从磁盘中读入内存,而后再返回.
  • 执行器拿到引擎给的行数据,把这个值加上1,好比原来是N,如今就是N+1,获得新的一行数据,再调用引擎接口写入这行新数据.
  • 引擎将这行新数据更新到内存中,同时将这个更新操做记录到redo log里面,此时redo log处于prepare状态.而后告知执行器执行完成了,随时能够提交事物.
  • 执行器生成这个操做的binlog,并把binlog写入磁盘.
  • 执行器调用引擎的提交事物接口,引擎把刚刚写入的redo log改为提交状态,更新完成.

将redo log的写入拆成两个步骤:prepare和commit ,这就是两阶段提交.服务器

若是不采用两阶段提交产生的后果:数据结构

  • 先写redo log后写binlog,假设redo log写完,binlog尚未写完的时候,mysql进程异常重启,redo log写完后,系统即便崩溃了,仍然可以把数据恢复回来,因此恢复后这一行c的值是1. 可是因为binlog没写完就crash了,这时候binlog里面没有这个记录这个语句,所以,以后备份日志的时候,存起来的binlog里面就没有这条语句果用这个binlog来恢复临时库的话,因为这个语句的binlog丢失,临时库就会少一次更新,恢复出来的这行c的值就是0,与原库的值不一样.
  • 先写binlog后写redo log,若是binlog写完以后crash,因为redo log还没写,崩溃恢复之后这个事务无效,因此这一行c的值是0,可是binlog里面已经记录了把c从0改为了1这个日志,因此,在以后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不一样.

04|深刻浅出索引(上)

常见的索引模型

哈希表架构

是一种以键-值(key-value)存储数据的结构,咱们只要输入待查找的值即key,就能够找到其对应的值即value.哈希的思路:把值放在数组里,用一个哈希函数把key换算成一个肯定的位置,而后把value放在数组的这个位置.

不可避免,多个key值通过哈希函数的换算,会出现同一个值的状况.处理这种状况的一种方法是,拉出一个链表.

哈希表这种结构适用于只有等值查询的场景,好比Memcached及其余一些NoSQL引擎.

有序数组 假设身份证号没有重复,这个数组就是按照身份证号递增的顺序保存的.这时候若是要查ID_card_n2对应的名字,用二分法就能够快速获得,这个时间复杂度是O(log(N)).

有序数组支持范围查询,要查询身份证号在[ID_car_X,ID_card_Y]区间的User,能够先用二分法找到ID_card_X(若是不存在ID_card_X,就找到大于ID_card_X的第一个User),而后向右遍历,直到查到第一个大于ID_card_Y的身份证号,推出循环.

若是仅仅看查询效率,有序数组就是最好的数据结构,可是,在须要更新数据的时候就麻烦了,往中间插入一个记录就必须得挪动后面全部的记录,成本过高.

因此,有序数组只适用于静态存储引擎,好比要保存的是2017年某个城市的全部人口信息,这类不会再修改的数据

二叉搜索树

二叉搜索树的特色是:每一个节点的左儿子小于父节点,父节点又小于右儿子.这样要查询ID_card_n2的话,按照图中搜索顺序就是按照UserA->UserC->UserF->User2这个路径获得,这个时间复杂度是O(log(N)).

固然为了维持O(log(N))的复杂度,须要保持这棵树是平衡二叉树,为了作这个保证,更新的时间复杂度也是O(log(N)).

二叉树是搜索效率最高的,可是实际上大多数的数据库存储不是使用二叉树,缘由是索引不止存在内存中,还要写到磁盘上.能够想象一颗100万节点的平衡二叉树,树高20,一次查询可能须要访问20个数据快.在机械硬盘时代,从机械硬盘随机读取一个数据块须要10ms左右的寻址时间.也就是说,对于一个100万行的表,若是使用二叉树来存储,单独访问一行可能须要20个10ms的时间,这样查询就比较慢了

为了让一个查询尽可能少的读磁盘,就必须让查询过程访问尽可能少的数据块.那么,咱们就不该该使用二叉树,而是使用"N"叉树,这里的N取决于数据块的大小.

以InnoDB的一个整数字段索引为例,这个N差很少是1200,这棵树高是4的时候,就能够存1200的2次方个值,这已是17亿了.考虑到树根的数据块老是在内存中,一个10亿行的表上一个整数字段的索引,查找一个值最多只须要访问3次磁盘.

InnoDB的索引模型

在InnoDB中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表.

每个索引在InnoDB里面对应一颗B+树

假设,咱们有一个主键列为 ID 的表,表中有字段 k,而且在 k 上有索引。

建表语句为:

mysql> create table T(
id int primary key, 
k int not null, 
name varchar(16),
index (k))engine=InnoDB;
复制代码

表中 R1~R5 的 (ID,k) 值分别为 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),两棵树的示意图以下:

根据叶子节点的内容,索引类型分为主键索引和非主键索引

主键索引的叶子节点存的是整行数据,在InnoDB里,主键索引也被称为聚簇索引

非主键索引的叶子节点内容是主键的值,在InnoDB里,非主键索引也被称为二级索引

主键索引和普通索引的查询区别:

  • 若是语句是select * from T where ID = 500,即主键查询方式,则只须要搜索ID这颗B+树;
  • 若是语句是select * from T where K = 5,即普通索引查询方式,则须要先搜索K索引树,获得ID的值为500,再到ID索引树搜索一次.这个过程称为回表. 也就是说,基于非主键索引的查询须要多扫描一颗索引树,所以,咱们在应用中应该尽可能使用主键查询

索引维护

自增主键的插入数据模式,符合递增插入,没插入一条新纪录,都是追加操做,都不涉及到挪动其余记录,也不会触发叶子结点的分裂

而用有业务逻辑的字段作主键,每每不容易保证有序插入,这样写数据成本相对较高

从存储空间的角度看,假设表中确实有一个惟一的字段,好比字符串类型的身份证号,那就应该用身份证号作主键?因为每一个非主键索引的叶子节点上都是主键的值.因此用身份证号作主键,那么每一个二级索引的叶子节点占用约20个字节,而若是用整型作主键,则只要4个字节,若是是长整型(bigint)则是8个字节

显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也越小

有些场景适合用业务字段直接作主键:

  • 一、只有一个索引
  • 二、该索引必须是惟一索引

InnoDB采用的是B+树结构,B+树可以很好的配合磁盘的读写特性,减小单次查询的磁盘访问次数,因为InnoDB是索引组织表,通常状况下建立一个自增主键,这样非主键索引占用的空间最小

05|深刻浅出索引(下)

如select * from T where k between 3 and 5; 建表语句:

mysql>create table T(
ID int primary key,
k int NOT NULL DEFAULT 0,
s varchar(16) NOT NULL DEFAULT '',
index k(k))
engine = InnoDB;

insert into T values(100,1,'aa'),(200,2,'bb'),(300,3,'cc'),(400,4,'dd'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg')
复制代码

sql语句的执行流程:

  • 一、在k索引树上找到k = 3的记录,取得ID=300;
  • 二、再到ID索引树查到ID=300对应的R3;
  • 三、在K索引树下取下一个值k=5,取得ID=500;
  • 四、再回到ID索引树查到ID=500对应的R4;
  • 四、在k索引树取下一个值k=6,不知足条件,循环结束.

在这个过程:回到主键索引树搜索的过程,称为回表

覆盖索引

select ID from T where between 3 and 5,这时只须要查ID的值,而ID的值已经在K索引树上了,所以能够直接提供查询结果,不须要回表,也就是说在这个查询里,索引k已经“覆盖了”咱们的查询需求,咱们称为覆盖索引.

覆盖索引能够减小树的搜索次数,显著提高查询性能,因此使用覆盖索引是一个经常使用的性能优化手段

最左前缀原则

B+树这种索引结构,能够利用索引的‘最左前缀,来定位记录’

在创建联合索引的时候,如何安排索引内的字段的顺序,第一原则是:若是经过调整顺序,能够少维护一个索引,那么这个顺序每每就是须要优先考虑采用的

在MySQL5.6引入的索引下推优化,能够在索引遍历过程当中,对索引中包含的字段先作判断,直接过滤掉不知足条件的记录,减小回表次数

对于例子中的InnoDB表T,若是要重建索引K,sql语句能够是:

alter table T drop index k;
alter table T add index(k);
复制代码

若是重建主键索引,能够这么写

alter table T drop primary key;
alter table T add primary key(id);
复制代码

对于这两种重建索引的作法,重建索引k的作法是合理的,可是重建主键的过程不合理,不管是删除主键仍是建立主键,都会将整个表重建.能够用alter table T engine=InnoDB替代

06|全局锁和表锁:给表加个字段怎么有这么多阻碍

根据加锁的范围,MySQL里面的锁大体能够分为全局锁、表级锁和行锁三类

全局锁

mysql加全局读锁的方法:Flush talbes with read lock(FTWRL),

全局锁的典型使用场景,作全库逻辑备份,也就是把整库每一个表都select出来存成文本.

mysql官方自带的逻辑备份工具是mysqldump,当mysqldump使用参数-single-transaction的时候,导数据以前就会启动一个事物,来确保拿到一致性视图,因为mvcc的支持,这个过程当中数据是能够正常更新,可是single-transaction方法只适用于全部的表使用事物引擎的库不支持事物的引擎,那么备份就只能经过FTWRL方法

表级锁

表级锁有两种:表锁和元数据锁

表锁的语法是lock tables ... read/write ,释放锁:unlock tales

另外一类表级的锁是MDL,MDL的做用是,保证读写的正确性,在mysql 5.5版本中引入来MDL,当对一个表作增删改查操做的时候,加MDL读锁;当对表作结构变动操做的时候,加MDL写锁.

  • 读锁之间不互斥,所以能够有多个线程同时对一张表增删改查.
  • 读写锁之间、写锁之间是互斥的,用来保证变动表结构操做的安全性,所以,若是有两个线程要同时给一个表加字段,其中一个要等另外一个执行完才能开始执行.

如何安全地给小表加字段: 首先解决长事物,事物不提交,就会一直占着MDL锁;

在alter table语句里面设定等待时间,若是在这个指定的等待时间里面可以拿到MDL写锁最好,拿不到也不阻塞后面的业务语句,先放弃,以后开发人员或者DBA再经过重试命令重复这个过程.

07|行锁功过:怎么减小行锁对性能的影响?

在InnoDB事物中,行锁是在须要的时候才加上的,当并非不须要了就马上释放,而是要等到事物结束时才释放,这个就是两阶段锁协议

若是事物中须要锁多个行,要把最可能形成锁冲突、最可能影响并发度的锁尽可能日后放.

死锁和死锁检测

当出现死锁后,有两种策略:

  • 一种策略是,直接进入等待,直到超时,这个超时时间能够经过参数innodb_lock_wait_timeout来设置.
  • 另外一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事物,让其余事物得以继续执行.将参数innodb_deadlock_detect设置为on,表示开启这个逻辑.

09|普通索引和惟一索引,应该怎么选择?

因为惟一索引用不上change buffer的优化机制,所以若是业务能够接收,从性能角度出发建议使用非惟一索引.

若是某次写入使用了change buffe机制,以后主机异常重启,是否会丢失change buffer和数据?

虽然只是更新内存,可是在事物提交的时候,咱们把change buffer的操做也记录到redo log里了,因此崩溃恢复的时候,change buffer也能找回来.

10|MySQL为何有时候会选错索引

咱们平时不断地删除历史数据和新增数据,MySQL可能会选错索引.

索引选择异常和处理

  • 采用force index强行选择一个索引
  • 能够修改语句,引导mysql使用咱们指望的索引,如将order by limit 1改为order by b,a limit 1
  • 新建一个更合适的索引,来提供给优化器作选择,或删除误用的索引

11|怎么给字符串字段加索引

例如,咱们要根据邮箱查学生的信息,若是没有建立索引,那么就会全表扫描,若是在邮箱上建立索引

一、alter table SUser add index index1(email);

二、alter table SUser add index index2(email);

第一个语句建立在index1索引里面,包含了每一个记录的整个字符串;而第二个语句建立的index2索引里面, 对于每一个记录都是只取前6个字节.

index1(即email整个字符串的索引结构):

  • 从index1索引树找到知足索引值'zhangsanxyz@qq.com'的这条记录,取得ID2的值;
  • 到主键上查到主键值是ID2的行,判断email的值是否正确的,将这行记录加入结果集;
  • 取index1索引树上刚刚查到的位置的下一条记录,发现已经不知足email= zhangsanxyz@qq.com'的条件了,循环结束.

index2(即email(6)索引结构):

  • 从index2索引找到知足索引值是'zhangs'的记录,找到第一个是ID1;
  • 到主键上查到主键值是ID1的行,判断出email的值不是'zhangsanxyz@qq.com',这行记录丢弃;
  • 取index2上刚刚查到的位置的下一条记录,发现仍然是'zhangs',取出ID2,再到ID索引上取整行而后判断,此次值对了,将这行记录加入结果集;
  • 重复上一步,直到index2上取到的值不是'zhangs'时,循环结束;

使用前缀索引, 定义好长度,既能够节省空间,又不用额外增长太多的查询成本.

前缀索引对覆盖索引对影响

对比如下两个sql语句:

select id,email from SUser where email='zhangssxyz@qq.com'

select id,email,name from SUser where email='zhangssxyz@qq.com'

使用index1能够利用覆盖索引,从index1查询到结果后直接就返回了,不须要回到ID索引再去查一次.而若是使用index2的话,就不得不回到ID索引再去判断email字段的值.

即便将index2定义的定义修改成email(18)的前缀索引,这时候虽然index2已经包含了全部的信息,但InnDB仍是要回到id索引再查一下,由于系统并不肯定前缀索引的定义是否截断了完整信息.

小结:

  • 直接建立完整索引,这样可能比较占用空间;
  • 建立前缀索引,节省空间,但会增长查询扫描次数,而且不能使用覆盖索引;
  • 倒序存储,在建立前缀索引,用于绕过字符串自己前缀的区分度不够的问题;
  • 建立hash字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式同样,都不支持范围扫描

13|为何表数据删掉一半,表文件大小不变

InnoDB的数据是按页存储的,删掉一个数据页上的全部数据记录,整个数据页就能够被复用. 可是数据页的复用和记录的复用不一样,若是把ID为400的行删除,再插入一个ID是800的行,就不能复用这个位置.而当整个页从B+树里摘掉之后,能够复用到任何位置,若是将数据页pageA上的全部记录删除后,pageA会被标记为可复用,这时候若是要插入一条ID=50的记录须要用新页的时候,pageA是能够被复用的.若是相邻的两个数据页利用率都很小,系统就会把这两个页上的数据合到其中的一个页上,另一个数据页也就被标记为可复用.所以delete命令其实只是把记录的位置或数据页标记为了“可复用”,但磁盘文件大小是不会变的.

重建表能够解决数据空洞的问题(pageA满了,在插入一个ID是500的数据时,就不得不申请一个新的页面pageB来保存数据了,页分裂完成后,pageA的末尾就留下空洞了)

22|MySQL有哪些“饮鸠止渴”提升性能的方法?

短连接风暴

** 解决办法**

先处理掉哪些占着的链接可是不工做的线程

使用show processlist查看哪些事物是空闲的,

减小链接过程的消耗

有些业务代码会在短期内先大量申请数据库链接作备用,若是确认是被数据库打挂了,那么一种可能的作法是让数据库跳过权限验证阶段.

慢查询性能问题

在mysql中,会引起性能问题的慢查询,大致有如下三种可能:

  • 索引没有设计好
  • sql语句没有写好
  • mysql选错了索引

对于索引没有设计好,解决方法:假设服务器是一主一备,主库A、备库B,能够如今备库上执行set sql_log_bin=off ,也就是不写binlog,而后执行alter table语句加上索引;执行主备切换;这时候主库是B,备库是A,在A上执行set sql_log_bin=off,而后执行alter table语句加上索引,更好的方案是相似gh-ost这样的方案,更加稳妥.

针对sql语句没有写好,解决方法:mysql 5.7提供query_rewrite功能,能够把输入的一种语句改写成另一种模式 好比语句错误地写成了select * from t where id + 1=10000,能够经过insert into query_rewrite.rewrite_rules(pattern,replacement,pattern_database) values("select * from t where id + 1= ?","select * from t where id = ? - 1","db1"); call query_rewrite.flush_rewrite_rules();

call query_rewrite.flush_rewrite_rules();这个存储过程,是让插入的新规则生效,也就是“查询重写”

上线前能够在测试环境把慢查询日志(slow log)打开,而且把long_query_time设置成0,确保每一个语句都会被记录入慢查询日志;

相关文章
相关标签/搜索