总结一下:这本书都讲解了那些知识点:

增、删、改、查 底层实现和优化
 索引:底层数据结构实现、聚簇索引(主键索引)、二级索引(索引、联合索引、前缀索引、唯一索引)使
    用方法和底层实现
 Mysql锁的使用:表锁、行锁、全局锁
          事务
 MySQL上线后的快速优化(饮鸩止渴)的办法
 MySQL主从一致和高可用优化解决办法
 MySQL的语法:count,join,order by 
 MySQL常见问题

说一下关系型数据库和非关系型数据库的区别

非关系型数据库的优势:

性能:NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过像关系型数据库需要进行多表查询,仅仅需要根据key来取出对应的value值即可,性能比较高

可扩展性:同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

使用场景:对这种一致性的要求不是那么的严格,允许有一定的时间间隔,日志、埋点、论坛、博客等

缺点:但是由于Nosql约束少,所以也不能够像sql那样提供where字段属性的查询。因此适合存储较为简单的数据。有一些不能够持久化数据,所以需要和关系型数据库结合。

关系型数据库的优势:

复杂查询:可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询

事务支持:使得对于安全性能很高的数据访问要求得以实现。

使用场景:所有有逻辑关系的数据存储

瓶颈:(1 )海量数据的读写效率,对于网站的并发量高,往往达到每秒上万次的请求,对于传统关系型数据库来说,硬盘I/o是一个很大的挑战。(2)高扩展性和可用性,在基于web的结构中,数据库是最难以横向拓展的,当一个应用系统的用户量和访问量与日俱增的时候,数据库没有办法像web Server那样简单的通过添加更多的硬件和服务节点来拓展性能和负载能力。

关系型数据库的最大优点就是事务的一致性,这个特性,使得关系型数据库中可以适用于一切要求一致性比较高的系统中。比如:银行系统。

1.一条SQL查询语句如何执行   

Mysql可以分为Server层和存储引擎层

Server:连接器、查询缓存、分析器、优化器、执行器

存储引擎:支持innodb、MyISAM等多个引擎   engine=memory

不同的存储引擎公用一个server层

连接器

负责跟客户端建立连接、获取权限、维持和管理连接,连接命令中的mysql是客户端工具,用来跟服务端建立连接,在完成经典的TCP握手后,连接器就要开始认证你的身份,然后输入账号密码,通过后会到权限表里面查看你拥有的权限。

注意:在数据库里面,长连接是指连接成功,如果客户端持续有请求,则一直使用同一个连接,短连接是指每次执行很少的几次查询就断开连接,尽量使用长连接

使用长连接会发现Mysql占用内存涨的特别快,这是因为MySQL在执行过程中临时用的内存是管理在连接对象里面,这些资源会在连接断开时候释放,如果长连接累积下来,导致内存占用太大,被系统强行杀掉(OOM),从现象看就是MYSQL异常重启

解决方案:

1>定期断开长连接或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连

2>如果使用5.7或更新版本,可以在每次执行一个比较大操作通过执行mysql_reset_connection重新初始化连接资源,这个过程不需要重连和重新做权限认证,但是会将连接回复到刚刚创建完成状态

查询缓存:

   之前执行过的语句及结果会以key-value对的形式,被直接缓存在内存中。key是查询的语句,value是查询结果,如果没有命中直接继续后面的执行阶段

但是大多数情况不建议使用查询缓存,因为往往弊大于利 :因为查询缓存失效非常频繁,只要对一个表的更新,这个表中所有的查询缓存都会被清空,对于更新压力大的数据库来说,查询缓存的命中率特别低。除非是一张静态表,很长时间更新。(关闭:query_cache_type:demand),当确定使用查询缓存的语句,可以用SQL_CAHCE显示指定: select SQL_CACHE * from T where ID=10                  但是在MYSQL 8.0版本直接将查询缓存整块功能删除掉了

分析器:

先做词法分析,比如输入由多个字符串和空格组成的一条SQL语句,MYSQL需要识别里面字符串分别是什么,代表什么,从输入的关键字如select,识别出来这是一个查询语句,再把字符串 “T” 识别表名 id识别为列ID

再做语法分析:语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。

分析器负责词法分析和语法分析,构造一颗解析树,整棵树只确保没有语法错误(语法分析),比如检查标识符是否有效,语句是否闭合等等

优化器:

优化器在表里面如果多个索引的时候,觉得执行哪个索引等等为sql选择执行计划

执行器: 

首先判断有没有执行查询的权限,(在工程实现上,如果命中查询缓存,会在查询缓存返回结果时候做权限认证,查询也会在优化器之前调用precheck验证权限),如果有权限打开表,执行器根据表的引擎定义去使用这个引擎提供的接口,然后执行语句,在语句执行过程中扫描多少行获取数据时,就在慢查询日志中rows_examined字段累加的

但是有些场景,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟rows_examined并不是完全相同

 2.一条SQL更新语句是如何执行的

       在一个表中有更新的时候,跟这个表相关的查询缓存会失效,所以这条语句就会把表上所有缓存结果清空,这是不建议使用查询缓存的原因。   接下来,分析器会通过词法和语法分析出来这是一条更新语句。优化器再决定使用哪个索引给出执行计划。然后,执行器负责具体执行,知道这一行,然后更新

在更新流程设计两个重要日志模块 redo log (重做日志)  binlog (归档日志)

redo log :

在Mysql中,如果每一次的更新操作都需要写入磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、查找成本都很高,为了解决这个问题,MYSQL利用了WAL技术,全程 write-Ahead Logging,它的关键点就是先写日志,再写磁盘

具体来说当一条记录需要更新的时候,innodb引擎会把记录写到redo log里面,并更新内存,同时innodb引擎会在适当的时候,将这个操作记录更新到磁盘。 redo log 是固定大小的,如图,从头开始写,写到末尾又回到开头循环写:

write pos 是当前记录的位置,一边写一边后移,写到第三个文件末就回到0号文件开头。checkpoint是当前要擦除的位置

有了redo log,innodb就可以保证即使数据库发送异常重启,之前提交的记录都不会丢失,因为redo log记录了已经成功提交事务的修改信息,并且把redo log持久化到磁盘,系统重启后读取redo log 恢复最新数据,这个能力称为 crash-safe(redo log是用来恢复数据的 用于保障,并且承担已提交事务的持久化特性)

 日志模块: binlog

在mysql的两层架构 server层,主要做的是MYSQL功能层面的事情 二、引擎层,负责存储相关的具体事宜。 redo log是innodb引擎特有的日志,而server层也有自己的日志成为bin log (归档日志)

因为一开始mysql里面并没有innodb,但是myisam没有crash-safe功能,binlog只能用于归档,而Innodb是另一个公司以插件形式引入mysql,所以innodb使用另一套日志系统实现crash-safe能力

区别:

1>redo log 是innodb特有的;binlog是mysql的server层实现的,所有引擎都可以用

2>redo log 是物理日志,记录的“在某个数据页做了什么修改”,是循环写的,空间固定会用完;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给id=2”这一行的c字段加1,binlog是可以追加的,并不会覆盖以前的日志

执行器和innodb引擎在执行update内部流程:

执行器和innodb引擎在执行update语句的内部过程:
   1>执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索找到这一行。如果这一行所在数据本来就在内存中就直接返回给执行器,否则需要从磁盘读入内存,然后再返回
   2>执行器拿到引擎给的行数据,修改后再调用引擎接口写入这行新数据
   3>引擎把这行新数据值更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态,然后告知执行器执行完成,随时可以提交事务
   4>执行器生成这个操作的binlog,并写入磁盘
   5>执行器调用引擎的提交事务接口,引擎把刚才写入的redo log 改成提交(commit)状态,更新完成

必须要两阶段提交,这是为了两份日志之间的逻辑一致:
  1>如果先redo log 再binlog
     如果在redo log 写完,binlog还没写完mysql进程异常重启,然后恢复之后,redo log仍然可以数据恢复,但是由于binlog还没写完就creash了,这个时候binlog里面没有记录这个语句。之后的备份日志,也是没有这个语句,如果需要binlog进行恢复临时表的话,就会因为缺少这次更新和原库的值不同
  2>如果先写binlog后写redolog
    如果在binlog写完之后crash,由于redolog还没写完,崩溃恢复以后这个事务无效,所以不会更新。但是再之后用binlog来恢复的时候就多了一个事务出来,与原库的值不同
   其实不只是误操作需要这个过程恢复数据,当扩容的时候用到全量备份加上应用Binlog实现
  
两阶段提交时跨系统维持数据逻辑一致性常用的一个方案

物理日志redo log 和逻辑日志 bin log 前者用于保证crash-safe能力
  sync_binlog这个参数设置为1,表示每次事务的binlog持久化磁盘,保证mysql异常重启之后binlog数据不丢失
   一般设置参数 innodb_flush_log_at_trx_conmmit=1 表示每次事务redo log直接持久化到磁盘,保证mysql异常重启之后数据不丢失

扩展:

1、 为了最大程度避免数据写入时io瓶颈带来的性能问题,MySQL采用了这样一种缓存机制:当query修改数据库内数据时,InnoDB先将该数据从磁盘读取到内存中,修改内存中的数据拷贝,并将该修改行为持久化到磁盘上的事务日志(先写redo log buffer,再定期批量写入),而不是每次都直接将修改过的数据记录到硬盘内,等事务日志持久化完成之后,内存中的脏数据可以慢慢刷回磁盘,称之为Write-Ahead Logging。事务日志采用的是追加写入,顺序io会带来更好的性能优势。

为了避免脏数据刷回磁盘过程中,掉电或系统故障带来的数据丢失问题,InnoDB采用事务日志(redo log)来解决该问题。

2、正是由于binlog有归档的作用,所以binlog主要用作主从同步和数据库基于时间点的还原。redo log 能够保证MySQL在任何时间段突然奔溃,重启后之前提交的记录都不会丢失。

参考地址:https://zhuanlan.zhihu.com/p/142491549

3、事务隔离: 为什么你改了我看不见

事务时保证一组数据库操作,要么全部成功,要么全部失败,在MySql中,事务支持是在引擎层实现的。

隔离性与隔离级别

ACID:原子性、一致性、隔离性、持久性
  当数据库有多个事务同时执行的时候,就可能出现脏读、不可重复读、幻度的问题,为了解决这些问题,就有了隔离级别的概念
  SQL标准的事务隔离级别包括:读未提交(RU)、读提交(RC)、可重复读(RR)、串行读(SZ)
  隔离级别越高,效率越低
  读未提交:一个事务还没提交时,它所做的变更就被别的事务看到
  读提交:一个事务提交以后,它的变更才会被其他事务看到
  可重复读:一个事务执行过程看到的数据,总是跟这个事务在启动时看到的数据是一致,未提交变更对其他事务也是不可见的
  串行读:写会加写锁,读会加读锁,当出现读写锁冲突后访问事务必须等前一个事务执行完成才能继续执行
   实现原理:在实现上数据库里面创建一个视图,访问的时候以视图的逻辑结果为准
  读未提交:直接返回记录的最新值,没有视图概念

读提交:这个视图在每个SQL语句开始执行的时候创建
  可重复读:视图在事务启动时创建,整个事务存在期间都在使用这个视图
   串行化:直接加锁的方式来避免并行访问
  注意:Oracle默认是读提交,因此从Oracle迁移到MySQL的应用,为保证数据库隔离级别一直,记得将MYSQL隔离级别设置为读提交
  配置方式:tarnsaction-isolation:READ-COMMITTED  show variables查看当前值
  
  需要可重复读场景:
     假设管理银行账户表,一个表存了每个月月底月,一个表存了账单明细,这个时候要做数据校对,判断上个月余额和当前余额的差额,是否与本月账单明细一直。所以希望在校对过程用户发生一笔新的交易也不影响结果

  事务隔离的实现:
     在mysql实际上每条记录在更新的时候会同时记录一条回滚操作,可以得到前一个状态的值
     如图一个值从1顺序改为2.3.4

当前值是4,但是在查询这条记录时候,不同时刻启动事务会有不同的rv,同一条记录在系统可以在系统存在多个版本,这就是数据库的多版本并发控制(MVCC)
  
  为什么少使用长事务:
     长事务以为着系统里面存在很老的事务视图,由于这些事务随着可能访问数据库里面任何数据,所以在这个事务提交之前,数据里面它可能用到回滚记录都必须保留,导致大量占用存储空间
     
     在mysql5.5以之前回滚日志跟数据字典一起放在ibdata文件里面,即使长事务最终提交,回滚段被清理,文件也不会变小。(有时候文件只有20GB,而回滚段有200GB的库,最后为了清理回滚段,重建整个库)

除了回滚段影响,长事务还占用锁资源,可能拖垮整个库
  
  
   对于一个MYSQL数据库(InnoDB),事务的开启与提交模式无非下面这两种情况:

1>若参数autocommit=0,事务则在用户本次对数据进行操作时自动开启,在用户执行commit命令时提交,用户本次对数据库开始进行操作到用户执行commit命令之间的一系列操作为一个完整的事务周期。若不执行commit命令,系统则默认事务回滚。总而言之,当前情况下事务的状态是自动开启手动提交。

 2>若参数autocommit=1(系统默认值),事务的开启与提交又分为两种状态:

①手动开启手动提交:当用户执行start transaction命令时(事务初始化),一个事务开启,当用户执行commit命令时当前事务提交。从用户执行start transaction命令到用户执行commit命令之间的一系列操作为一个完整的事务周期。若不执行commit命令,系统则默认事务回滚。

②自动开启自动提交:如果用户在当前情况下(参数autocommit=1)未执行start transaction命令而对数据库进行了操作,系统则默认用户对数据库的每一个操作为一个孤立的事务,也就是说用户每进行一次操作系都会

 启动方式:
     1>显式启动事务 begin 或者 start transaction  提交语句commit 回滚语句 Rollback
     2>set autocommit=0 会将这个线程自动提交关掉。意味着如果只执行一个select语句,事务就启动了而且不会自动提交,这个事务持续存在直到你主动执行commit或rollback语句或者断开连接。
     
     有些客户端连接框架模式先执行这个命令,如果是长连接,导致意外的长事务
      
      如果是set autocommit=1  手动提交:可以显式语句方式启动  自动提交:如果用户在当时情况下未执行显示语句而对数据库进行了操作,系统默认用户对数据的每个操作作为一个孤立的事务,用户每进行一次操作即时提交或者即时回滚。这种情况下用户的每一个操作都是一个完整的事务周期。

建议设置set autocommit=1,通过显式语句方式启动,如果担心多一个交互的问题(可以让每次事务开始都不需要主动执行一次begin),可以使用commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行begin语句的开销。同时明确的指导每个语句是否处于事务中
       可以在infomation_schema库的innodb_trx这个表中查询长事务

6、全局锁和表锁:给表加个字段怎么这么多阻碍

数据库锁的设计的初衷是处理并发问题,当出现并发的时候,合理地控制资源的访问规则
    
    根据加锁的范围,MySQL里面的锁大致分为 全局锁、表级锁和行锁三类
    
    全局锁:对整个数据库实例加锁
      命令: flush tables with read lock (FTWRL)
      让整个库处于只读状态,之后其他线程以下语句会被阻塞:数据更新语句(增删改)、数据定义语句(建表、修改表结构)和更新类事务提交语句

全局锁典型使用场景是:全库的逻辑备份

但是让整库都只读,很危险:
        如果主库上备份,那么备份期间不能执行更新,业务基本上就得停
        如果从库备份,那么备份期间不能执行主库同步过来的binlog,会导致主从延迟

但是不加锁的话,备份系统得到的库不是一个逻辑时间点的,这个视图是逻辑不一致的
     在官方自带的逻辑备份工具是 mysqldump。当mysqldump使用参数-single-transaction的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。

   为什么还需要FTWRL?
     一致性读是好,但前提是引擎要支持这个隔离级别。比如对于MyISAM这种不支持事务的引擎,如果备份过程有更新,就需要用到这个命令,所以,single-transaction方法只适用于所有的表使用事务引擎的库

   既然要全库只读,为什么不适用set global readnonly=true的方式呢?
      注意:read_only=1只读模式,不会影响slave同步复制的功能,可以限定普通用户进行数据修改的操作,但不会限定具有super权限的用户的数据修改操作;
     一:有些系统,readonly的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库,因此修改了变量影响面更大
     二:在异常处理机制上有差异,如果执行FTWRL命令之后由于客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为readonly之后,如果客户端发生异常,则数据库一直保持readonly状态,导致整个库长时间处于不可写状态

表级锁:mysql里面表级别的锁有两种:表锁、元数据锁(MDL)
    
  表锁的语法: lock tables ....read/write  可以用unlocak tables主动释放锁,也可以客户端断开的时候自动释放,lock tables语法除了会限制别的线程读写外,也限定了本线程接下来的操作对象

比如:lock tables t1 read,t2 write
     则其他线程写t1、读写t2的语句就会被阻塞。同时,线程A在执行unlock tables之前,也只能执行读t1、读写t2的操作。连写t1都不允许。

而对于Innodb这种支持行锁的引擎,一般不适用lock tables命令来控制并发。

  MDL:不需要显示使用,在访问一个表的时候被自动加上
  MDL作用保证读写的正确性,避免一个查询表中的数据,在执行期间另一个线程对这个表结构做变更而导致的结果不同

在Mysql5.5之后引入了MDL,当对一个表做增删改查时,加MDL读锁;当要对表做结构变更,加MDL写锁
    读锁之间不互斥,因此可以多个线程同时对一张表增删改查
    读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此如果有两个线程同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

给一个小表加字段,导致整个库挂了?
      因为给一个表加字段或者修改字段,加索引,需要扫描全表数据,在大表操作的时候,特别小心,即使是小表,操作不慎也会出问题 比如:


      如图:SessionA先启动,这时候会对t加MDL读锁。由于sessionB需要的也是读锁,因此可以正常执行,之后sessionC会被blocked,是因为sessionA的读锁还没释放,而C需要MDL写锁,因此被阻塞,但之后所有在表上新申请的MDL读锁请求也会阻塞,这个表显示就时完全不可读写
      如果这个这个表的查询讵频繁,客户端有重试机制,也就是说超时候再起一个新session请求的话,这个库的线程很快就会爆满。
      事务中的MDL锁,在语句开始时申请,语句结束后并不会释放,等到整个事务提交后再释放

  如何安全给小表加字段?
        首先要解决长事务,事务不提交,就会一直占着MDL锁,在MYSQL的information_schema库的innodb_trx表中,可以查到当前执行中的事务。做DDL变更的表如果刚好偶遇长事务可以考虑暂停DDL,或者kill这个长事务
        
      如果是一个热点表,虽然数据量不大,但是请求频繁的话呢?
        这个时候kill未必管用,因为新的请求马上来了。
        理想的机制,在alter table语句里面设定等待时间,如果等到时候里面能够拿到MDL写锁最好,拿不到不要阻塞后面的业务语句,然后通过重试命令重复这个过程

7、行锁:怎么减少行锁对性能的影响

Mysql的行锁是在引擎层由各个引擎自己实现的,并不是所有引擎都支持行锁。 innodb行级锁是通过锁索引记录实现的,如果upadate的列没建索引,即使只update一条记录也会锁定整张表。
   两阶段锁:在Innodb事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是等到事务结束才释放,这个就是两阶段锁协议。

根据这个设定,如果事务中需要锁多个行,要把最可能造成锁冲突,最可能影响并发度的锁尽量往后放。比如:
     1>a付款买电影票
     2>b影院账户余额增加这个票钱
     3>记录日志中心
    如果c再买票,那么这两个失误冲突的部分就是2>了,根据两阶段锁协议,只有再提交事务才开始释放行锁,所以按照3>、2>、1>这个顺序就是行锁时候最少,很大程度减少锁等待,提升了并发

  死锁和死锁检测:

当并发系统中不同线程出现循环资源依赖,涉及的线程都是在等待别的线程释放资源时,就会导致这几个线程进入无线等待的状态,称为死锁
    比如:


    如图:A事务等待事务B释放id=2行锁,而B等待A释放id=1行锁

如果这个影院做活动,在一天中,mysql就挂掉了,登上服务器一看,CPU消耗接近100%,但整个数据库每秒执行不到100个事务,这是什么原因?

  如果出现死锁有两种策略:
     一种是,直接等待,直到超时,这个超时时间通过参数innodb_lock_wait_timeout设置
    
    另一种是,发起死锁检测,发现死锁后,主动回滚锁链条中某一个事务,让其他事务得以继续执行,将参数innodb_deadlock_detect设置为on,表示开启这个逻辑
    死锁检测实现:在每条事务执行前,如果加锁访问的行上有锁,才会进行死锁检测

在innodb默认的超时时间是50s,如果出现死锁后,第一个被锁定的线程要过50s才会被超时退出,然后其他线程才有可能继续执行。对于在线法务这个时间无法接受,但是不能设置为很小的时间,虽然死锁的很快解开,但是会出现很多误伤

因此正确情况是采用第二个策略,这个默认值是开启的,主动监测在发生死锁的时候,是能够快速发现并进行处理的,但是有额外的负担,比如每当一个事务被锁的时候,就要看看它锁依赖的线程有没有被其他锁住,如此循环,最后判断是否出现循环等待,也就是死锁

比如场景中所有事务都要更新同一行场景的话,每个新来的被堵住的线程就会判断是不是自己加入导致了死锁,时间复杂度为On,如果1000并发同时更新一行,那么死锁检测就会是100W这个量级。这就造成消耗大量CPU,CPU利用率很高,但是每秒却执行不了几个事务

解决由这种热点行更新导致问题的方案?   问题在于死锁检测要耗费大量的CPU资源

方法一:确保这个业务不会出现死锁,可以临时把死锁检测关掉。
       风险:意味着出现大量的超时

方法二:控制并发度。比如同一行同时只有10个线程更新那么死锁检测成本很低
        这个并发控制要做在数据库服务端。因为如果在客户端的话,但是很快发现这个方法不可行,因为客户端很多,比如一个应用有600个客户端,即使每个客户端控制只有5个并发编程,汇总到数据库服务端一行,峰值并发也可能到3000

因此可以在中间件实现,还可以通过修改mysql源码,基本思路就是,对于向同行的更新,在进入引擎之前排队,这样innodb内部就不会大量的死锁检测工作
     
    方法三:可以根据业务,将一行逻辑改为多行减少锁冲突。以上个场景为例,可以考虑防在多个记录中,影院总额是这10个记录的值综合。这样减少锁等待,也就减少了死锁检测CPU消耗

如果要删除一个表里面的10000行数据,有以下三种方法可以做到:

第一种:直接执行 delete from T limit 10000;

第二种:在一个连接中循环执行20次 delect from T limeit 500

第二种:在20个连接中同时执行delete from Tlimit 500

第一种,单个语句占用时间长,锁的时间也必将长;而且大事务还会导致主从延迟

第三种,会认为造成锁冲突,第二种是相对较好的

8、事务到底是隔离还是不隔离的

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作InnoDB表的语句,事务才能真正启动。如果想马上启动一个事务,可以使用start transaction with consistent snapshot这个命令

第一种启动方式,一致性视图在第执行第一个快照语句时创建; 第二种启动方式,一致性视图是在执行这个语句时创建的

注:start transaction with consistent snapshot 创建一个持续整个事务的一致性快照。但是在读提交下这个就没意义了

如果事务C没有显示使用begin/commit,表示update语句本事就是一个事务,语句完成自动提交。事务B在更新了行之后查询;事务A在一个只读事务中查询,而且时间顺序上是在事务B的查询之后。

结果是 B的k=3 A的k=1

在Mysql,有两个视图的概念

一个是view.它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。语法:create view ...而它的查询方法与表一样

另一种是InnoDB在实现MVCC时用到的一致性读视图。用于支持RC(读提交)和RR(可重复读)隔离级别的实现

   

快照在MVCC里面是怎么工作的?

在可重复读隔离级别下,事务在启动的时候就进行快照,这个快照是基于整库的。

在InnoDB里面每个事务有一个唯一的事务ID:transaction id,它是在事务开始的时候向InnoDB事务系统申请的,是按申请顺序严格递增的。

每行数据也都是有多个版本,每次事务更新数据的时候,都会生成一个新的数据版本,并且把transaction id赋值给这个数据版本的事务ID,记为row trx_id。同时旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它,也就是一个数据表中一行记录可能有多个版本row,每个版本有自己的row trx_id

如图:一个记录被多个事务连续更新后的状态,当前最新版本是v4,它是由transcation id为25的事务更新的,因此它的row trx_id也是25

什么是undo log?

   undo log叫做回滚日志,记录数据被修改前的信息,和重做日志记录(redo log)相反,重做日志记录数据被修改后的信息。undo log 主要记录的是数据逻辑变化,为了在发生错误时回滚之前的操作,需要将之前操作记录下来,然后发生错误时可以回滚(undo log是用来回滚数据的用于保障 未提交事务的原子性)

undo log(回滚日志) 在哪里?

图2中三个虚拟箭头就是undo log;而V1 、V2 V2并不是物理上真实存在的,而是每次需要的时候根据当前版本和undo log计算出来的。比如需要v2的时候,通过V4依次执行U3、U2算出来的。

按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见 在实现上InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在活跃的所有事务ID,这个活跃指的是启动了但没有提交的
例如:

MVCC在mysql中的实现依赖的是undo log与read view

undo log :undo log 中记录某行数据的多个版本的数据

read view :用来判断当前版本数据的可见性

事务在启动的时候就进行快照,这个快照是基于整库的如果整库是100G的数据呢?难道复制一遍?

例如一个事务,它的低水位为18,那么当它访问这一行数据,就会从v4通过u3计算出v3,所以它看到的值是11,所以,系统里随后发生的更新,跟这个事务看到的内容无关,对它来说这些新的数据版本是不存在的,所以这些事务的快照就是‘静态‘的,InnoDB利用了所有数据都有多个版本这个特性,实现了秒级创建快照的能力

如何更新数据呢?

例如上题:

A、B、C的版本号分别为100,101,102

比如事务A的视图数组为[99,100],其中99为创建事务时活跃事务ID,事务B视图数组为[99,100,101],事务C的视图数组[99,100,101,102]

在运行上:事务C先运行,这个时候把数据改为2,这个时候数据最新版本为102,接下来是事务B,把数据改为3,这个时候数据最新版本为101,当事务A运行的时候首先查看最新版本101,比高水位高,属于红色不可见,然后102,比高水位高,属于红色不可见,然后继续往前找,终于找到90,比低水位小,处于绿色可见,这样执行下来,虽然期间这一行数据被修改过,但是事务A不论在什么时候查询,看到这样数据结果都是一致的,我们称为一致性读

更新逻辑:但是事务B的update语句,如果按照一致性读,结果不对

事务B此时的set k = k+1是在(1,2)的基础上进行操作,所以用到了这个规则:更新数据都是先读后写,而这个读,只能读当前的值,称为当前读,除了update,select语句如果加锁,也是当前读 lock in share mode或者for update

如图:

这个时候因为两阶段锁协议,C要等B执行完,释放这个锁,才能继续它的当前读

事务的可重复读的能力是怎么实现的?

可重复读的核心就是一致性读;而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用,需要进入锁等待

读提交和可重复读区别:

在可重复读下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图

在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图

小结:InnoDB的行数据有多个版本,每个数据版本有自己的row trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据row trx_id和一致性视图确定数据版本的可见性

对于可重复读,查询只承认在事务启动前已经提交完成的数据;

对于读提交,查询只承认在语句启动前就已经提交完成的数据

而当前读,总是读取已经提交完成的最新版本。

为什么表结构不支持可重复读?

这是因为表结构没有对于的行数据,也没有row trx_id,因此只能遵循当前读的逻辑,当然8.0已经可以把表结构放在Innodb字典里了,也许以后会支持表结构的可重复读

扩展:

事务的实现

  • 事务的原子性是通过 undo log 来实现的,从而达到回滚

  • 事务的持久性性是通过 redo log 来实现的,从而达到故障后恢复

  • 事务的隔离性是通过 (读写锁+MVCC)来实现的,运用的优化思想有读写分离,读读并行,读写并行

  • 而事务的终极大 boss 一致性是通过原子性,持久性,隔离性来实现的,通过回滚,以及恢复,和在并发环境下的隔离做到一致性。

原子性:

1.每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上
2.所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。

为了做到同时成功或者失败,当系统发生错误或者执行rollback操作时需要根据undo log 进行回滚

回滚操作就是要还原到原来的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,比如:

(1) 如果在回滚日志里有新增数据记录,则生成删除该条的语句

(2) 如果在回滚日志里有删除数据记录,则生成生成该条的语句

(3) 如果在回滚日志里有修改数据记录,则生成修改到原先数据的语句

持久性的实现

事务一旦提交,其所作做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。

MySQL的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘IO,然而即使是使用SSD磁盘IO也是非常消耗性能的。 为此,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:
  读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
  写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;

这种缓冲池的措施虽然在性能方面带来了质的飞跃,但是当MySQL系统宕机,断电的时候可能会丢数据  所以使用了redolog  具体参考第二节

既然redo log也需要存储,也涉及磁盘IO为啥还用它?

(1)redo log 的存储是顺序存储,而内存(缓存)同步是随机操作。

(2)内存(缓存)同步是以数据页为单位的,每次传输的数据大小大于redo log。

隔离性实现

原子性,隔离性,持久性的目的都是为了要做到一致性,但隔离性跟其他两个有所区别,原子性和持久性是为了要实现数据的可性保障靠,比如要做到宕机后的恢复,以及错误后的回滚。

那么隔离性是要做到什么呢?  隔离性是要管理多个并发读写请求的访问顺序。 这种顺序包括串行或者是并行

RU:

事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。

因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行

换句话说,读的操作不能排斥写请求。

RU:优点:读写并行,性能高 缺点 :造成脏读

RC:该级别会产生不可重读以及幻读问题(不可重复读:在一个事务内多次读取的结果不一样)

原因:这跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本(视图)而是不同的副本(不同)

在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读

RR:mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC。

为什么能可重复度?只要没释放读锁,在次读的时候还是可以读到第一次读的数据。

优点:实现起来简单

缺点:无法做到读写并行

采用MVCC实现

为什么能可重复度?因为多次读取只生成一个版本,读到的自然是相同数据。

优点:读写并行

缺点:实现的复杂度高

但是在该隔离级别下仍会存在幻读的问题

SS:该隔离级别理解起来最简单,实现也最单。在隔离级别下除了不会造成数据不一致问题,没其他优点。

一致性:数据库总是从一个一致性的状态转移到另一个一致性的状态.

之发生异常了,银行卡的钱也不能平白无辜的减少,而是回滚到最初状态。

2.又或者事务提交之后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化。

4、深入浅出索引(上)

索引的出现就是为了提高数据查询效率,就像书的目录一样

索引的常见模型:哈希表、有序数组和搜索树


    哈希表:一种以键-值存储数据的结构,把值放在数组中,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置,当多个key值经过哈希函数的换算,会出现一个值的情况。处理这种情况的办法,拉出一个链表,如果位置相同,顺序遍历取到值(因为值并不是递增的,这样做的好处增加新的user时速度更快,只需要往后追加。但缺点是,因为不是有序,所以哈希索引做区间查询的速度会很慢)

 结论:哈希表这种结论适用于只能等值查询的场景(比如memcached及其他一些noSQL引擎)

  有序数组:
      而有序数组在等值查询和范围查询和范围查询场景中性能就非常优秀,可以通过二分法快速得到所需的值,这个时间复杂度是O(logn),并且支持范围查询,但是存在的问题就是:在中间插入一个记录就必须挪动后面所有的记录,成本太大

结论:所有有序数组索引只适用于静态存储引擎,比如保存的是17年某个城市所有人口信息,这类不会修改的数据

二叉搜索树:特点就是左儿子小于父节点,父节点又小于右儿子  时间复杂度为O(log(N))
      实际上大多数数据存储并不适用二叉树,其原因是:索引不只存在内存中,还要写在磁盘上。
       比如:一颗100万节点的平衡二叉树,树高20.一次查询可能需要访问20个数据块,在机械硬盘中,从磁盘读一个数据块需要10ms左右的寻址时间。
       也就是说如果使用二叉树来存储,单独访问一个行可能需要20个10ms时间,太慢。那么我们就不应该使用二叉树,而是使用N叉树,这里的N取决于数据块的大小

InnoDB索引模型:
       在Innodb中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表,一般情况建议创建自增主键,这样非主键索引占用的空间最小,然后数据存储在B+树中,每一个索引在Innodb里面对应一颗B+树

其中主键列为ID的表,表中有字段k,并且在k上有索引

 主键索引:叶子节点存的都是整行数据。在Innodb里,主键索引也被称为聚簇索引
   非主键索引:叶子节点的内容时主键的值。非主键索引也被称为二级索引

   基于主键索引和普通索引的查询有什么区别?
     主键查询只需要搜索ID这颗B+树就可以,而如果普通索引查询,则需要先搜索k索引树,得到ID的值,然后再到ID索引树搜索一次,这个过程称为回表,也就是基于非主键索引的查询需要多扫描一颗索引树

索引维护:
     B+树为了维护索引有序性,在插入值的时候做必要的维护。如果在当前ID前插入一条数据,需要逻辑上挪动后面的数据,空出位置,更糟的情况是,如果所在的数据页已经满了,根据B+树的算法,需要申请新的数据页,然后挪动部分数据过去,这个过程称为页分裂。性能必然会受影响,而且还会影响数据页的利用率,原本一个页的数据分到两个页中,空间利用率低大约50%

当相邻两个页由于删除数据,利用率很低后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程

一般自增主键的插入数据模式,符合递增插入场景,都是追加操作,不涉及到挪动其他记录,也不会触发叶子节点的分裂

并且如果是用身份证做主键,那么每个二级索引节点占用约20个字节,如果用整型做主键,则只要4个字节,如果长整型则是8个字节
     显然,主键长度越小,普通索引的叶子节点就越小,索引占用的空间也就越小

聚簇索引按照如下规则创建:
        当定义了主键后,InnoDB会利用主键来生成其聚簇索引;
        如果没有主键,InnoDB会选择一个非空的唯一索引来创建聚簇索引;
        如果这也没有,InnoDB会隐式的创建一个自增的列来作为聚簇索引。

为什么重建索引:

索引可能因为删除或者页分裂等原因,导致数据页有空洞,重建索引的过程会创建一个新的索引,把数据按顺序插入,这样的页面利用率最高,也就是索引更紧凑、更省空间

删除索引并重建,做法是合理的,可以达到省空间的目的,但是这样重建主键的过程不合理,不论是删除主键还是创建主键,都会将整个表重建,所以接着执行这两个语句,第一个语句就白做了。这两个语句,可以用这句代替 alter table T engine =InnoDB

5、深入浅出索引(下)

SQL查询语句的执行过程:
    在k索引树找到k=3的记录,取得ID=300
    再到ID索引树查到ID=300
    重复上两步
    在k索引树取下一个值k=6,不满足条件,循环结束
    这个过程中,回到主键索引树搜索的过程,称为回表

  覆盖索引
    如果把select *改为select ID,就可以直接提供查询结果,不需要回表,也就是索引k已经覆盖了我们查询需求,称为覆盖索引

覆盖索引:如果查询条件使用普通索引(或是联合索引的最左原则字段),查询结果是联合索引的字段或者主键,不用回表操作,直接返回结果,减少IO磁盘读写和读取行数据

覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段,比如一个高频请求,根据市民的身份证号查询他的姓名,这个联合索引就很有音译。它可以在这个高频请求上用到覆盖索引,不再需要回表查整行记录

代价:因为索引字段维护总有代价,因此,建立冗余索引来支持覆盖索引时就需要权衡考虑

数据量很大的时候,二级索引比主键索引更快,这个结论只有在覆盖索引时才成立,非覆盖索引还是药
  
  最左前缀原则:
    因为B+树这种索引结构,可以利用索引的最左前缀来定位记录
    例如:联合索引(name,age)

比如 where name like 张%,这时,查找第一个符合记录ID3,然后向后遍历,只要满足最左前缀,就可以利用索引来加速检索,所有有了a,b这个联合索引,就不需要再建立a这个索引了

在建立联合索引的时候如何安排索引内的字段排序
     第一原则:如果通过调整顺序,可以少维护一个索引,这个顺序往往是优先考虑采用的
     第二原则:索引占用空间问题。例如 name比age字段大,就创建一个(name,age)的联合索引和一个(age)单字段索引

索引下推:
   比如 like 张% and age=10

在5.6之前,只能从ID3开始一个个回表,到主键索引找到数据行,再对比字段值,而在MYSQL5.6引入了索引下推优化,可以在索引遍历过程中,对爆音中包含的字段先做判断,直接过滤掉不满条件的记录,减少回表次数

无索引下推:

有索引下推:

比如 like 张% and age=10

在5.6之前,只能从ID3开始一个个回表,到主键索引找到数据行,再对比字段值,而在MYSQL5.6引入了索引下推优化,可以在索引遍历过程中,对爆音中包含的字段先做判断,直接过滤掉不满条件的记录,减少回表次数

9、普通索引和一致性索引怎么选择

从性能上考虑的话:

如图为 InnoDB的索引组织结构

接下来对查询语句和更新语句的性能影响来进行分析

查询过程:

假设,执行查询的语句是select id from Twhere k=5.这个查询语句在索引树上查找的过程,先是通过B+树从树根开始,按层搜索到叶子节点,然后找到到如图的这个数据页,然后可以认为数据页内部通过二分法来定位记录

对于普通索引来说,查到了满足条件的记录(5,500),需要查找下一个记录,知道碰到第一个不满足k=5条件的记录

对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止检索

性能对比: 微乎其微

因为InnoDB的数据是按照数据页为单位来读写的。当需要读一条记录的时候并不是把这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存,在InnoDB,每个数据页的大小默认是16kB,因为引擎是安页读取,所以所在的数据页都在内存里,那么对于普通索引多一次查询操作在内存中只需要一次指针查找,当然如果读取的下一个记录必须要读取下一个数据页,这个操作就要从磁盘中获取,但是一个数据页可以防前个key,因此出现的几率会很低

更新过程:

当需要更新一个数据页,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InnoDB会将这些更新操作缓存在change buffer中,这样就不需要从磁盘中读入这个数据页。在下次查询需要访问这个数据页时候,将数据页读入内存,然后执行change buffer这个页有关的操作。

change buffer可以持久化数据,将changebuffer总操作应用到数据页,得到最新结果的过程称为merge。除了访问这个数据页会触发merge外,系统有后台线程会定期merge。在数据正常关闭的过程也会执行merge操作

将更新操作先记录在change buffer,减少读磁盘,语句执行速度会得到明显的提升,还可以避免占用内存,提高内存利用率

什么条件下可以使用change buffer呢?

对于唯一索引来说,所有的更新操作都要判断这个操作是否违反唯一性约束,必须将数据页读入内存才能判断。如果都已经读入到内存,那直接更新内存会更快,就没必要使用change buffer。所以唯一索引是用不到的

change buffer用的buffer pool里面的内存,不能无限增到,可以通过innodb_change_buffer_max_size来动态设置。如果参数为50,标识占用buffer pool的50%

如果在这张表中插入记录,InnoDB处理流程

第一种情况,如果这个记录要更新的目标在内存中:

对于唯一索引,找到位置,判断没有冲突,插入这个值,执行结束

对于普通索引,找到位置,插入这个值,执行结束

只是一个判断,耗费微小的CPU实际,几乎没差别

第二种情况,记录要更新的目标不再内存中

对于唯一索引,将数据页读入内存,判断有没有冲突,插入这个值,执行结束

对于普通索引,则是将更新记录在change buffer,执行结束

将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机磁盘访问,对更新性能提升会很明显

实例:一个同学反馈,他负责的某个业务库内存命中率突然从99%降低到了75%,整个系统处于阻塞状态,更新语句全部堵住,探究其原因后,发现业务有大量插入数据操作,而他在前一天把其中某个普通索引改成了唯一索引

change buffer使用场景:

只限于普通索引的场景不适用于唯一索引。

change buffer的主要目的就是将记录的变更动作缓存下来,索引在数据也做merge之前,change buffer记录的变更越多(也就是这个页面上要更新的次数越大),收益越大。 因此,对于写多读少的业务,页面写完之后马上被访问到几率比较少,使用效果最好,场景模型 账单类、日志类系统

反过来,写入马上查的话,记录在change buffer,但是由于马上访问这个数据页,会立即出发merge过程。这样随机访问IO次数不会减少,反而增加了change buffer的维护代价,所以对于这种业务模式,反而齐了副作用

对于这两类索引在查询能力上没什么差别,主要考虑的是对更新性能的影响,索引建议尽量使用普通索引

change buffer和redo log

   例如在表中执行插入k1,k2的语句

其中k1所在数据页在内存中,k2所在的数据页不再内存中

涉及四个部分: 内存、redo log (ib_log_fileX)、数据表空间(t,idb)、系统表空间(ibdata1)

1.page1 在内存中,直接更新内存

2.page2没有在内存中,就在内存的change buffer区域,加入插入信息

3.将上述两个动作记入redo log中

做完上面这些,事务就完成了,执行这条更新语句的成本很低,就是写了两处内存,然后一处磁盘(两次操作何在一起写了一次磁盘),而且还是顺序的

那在这之后的读请求怎么处理呢?

比如读k1,k2,如果读语句在更新语句发生不就,内存中数据都还在

1.读page1的时候,直接从内存返回(所以WAL之后如果读数据,不一定要读盘,不是一定要从redo log里面把数据更新以后才可以返回,如图,虽然磁盘还是之前数据,但是这里直接从内存返回结果)

2.读page2的时候,需要把page2从磁盘读入内存,然后应用change buffer里面操作日志,生成一个正确的版本返回结果

简单地对比两个机制在提升更新性能上的收益的话redo log 主要介绍的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读自盘的IO消耗

扩充

1>change buffer作用体现在针对普通索引(非主键的都是二级索引,二级索引又包括唯一索引和普通索引),change buffer前身是insert buffer,只能对insert操作优化,又来升级了,增加了update/delete支持,名字改为change buffer

2>主键id也是唯一索引,所以主键索引用不上change buffer,都是对于那些二级索引才有效

 

change buffer一开始是写内存的,那么如果这个时候机器断电重启,会不会导致丢失呢?再从磁盘读入数据可就没有merge过程,等于数据丢失了,会不会出现这种情况?

会导致change buffer中为完成操作数据丢失,但不会丢失已完成操作的,针对未写完的,此部分操作还未写入redo log,因此事务还未提交,所以没影响,针对已经写完的,在事务提交的时候,我们把change buffer的操作也记录到redo log里,所以崩溃恢复的时候,change buffer也能找回来

merge的过程是否会把数据直接写回磁盘?

merge的执行流程是这样的:

1、从磁盘读入数据页到内存(老版本的数据页)

2、从change buffer里找出这个数据页的change buffer 记录(可能有多个),依次应用,得到新版数据页

3、写redo log。这个redo log包含了数据的变更和change buffer的变更

这个时候,数据页和内存中change buffer 对应的磁盘位置都没有修改,属于脏页,之后各自刷回自己的物理数据

10、MySQL为什么有时候选错索引

优化器的逻辑:

首先,选择索引是优化器的工作,而优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句。

其中,在数据库里面,扫描行数,是否使用临时表、是否排序等因素进行判断来选择索引

 1、扫描行数是怎么判断的?

mysql在真正执行语句之前,并不能精确地知道满足这个条件记录有多少条,只能根据统计信息来估算记录数。这个统计信息就是索引的区分度,一个索引上不同的值越多,这个索引的区分度越好。一个索引上不同的值的个数,我们称之为基数,也就是说,这个基数越大,索引的区分度越好。

我们可以使用 show index from tables方法看一个索引的基数。在统计信息里,这三个索引并不同,其实都不准确。

  MySQL是怎么得到索引的基数呢?       

     MySQL采样统计的方法来得到,因为把整张表取出来一行行统计,虽然可以得到精确的结果,但是代价太高,所以选择采样统计,InnoDB默认会选择N个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到这个索引的基数。

维护索引统计信息:当变更的数据行数超过1/M的时候,会自动出发重新做一个次索引统计。

但是因为是采样统计,所以不管N是20还是8,这个基数都不准的

索引的统计值(cardinality列)虽然不够精确,但大体上还是差不多多,选错索引一定有别的原因其实索引统计只是一个输入,对于一个具体的数据,优化器还要判断,执行这个语句本身要扫描多少行

例如:

   row这个字段表示预计扫描行数

其中Q1的结构符合预期,rows的值为104620的,但是Q2的row值是37116,偏差就大了。而图一中我们使用explain命令看到的rows是只有10001行,是这个偏差导致优化器的判断

问题一:为什么会不准?

     实例:在sessionA开启了事务并没有提交,然后sessionB删掉了所有数据然后通过存储过程插入数据,看上去覆盖了原来的行数,但是因为sessionA并没有提交,所以之前插入的10万行数据是不能删除的。这个时候,之前的数据都有两个版本,所以,索引a上的数据其实就有两份。 如果是使用主键的话直接按照行数来估计,而表的行数,优化器直接用的是show table status的值

问题二为什么又会其不扫描37000行的呢?

这是因为如果使用索引a,每次从索引a上拿一个值都要回到主键索引上查出整行数据,这个代价优化器也算进去了,而如果选择扫描10万行,直接在主键索引上扫描,没有额外的代价,优化器会估算这两个选择的代价,从结果来看,认为直接扫描主键索引更快,显然,从执行时间来看,这个选择不是最优的

既然是统计信息不对,使用analyze table t 可以重新统计索引信息,可以解决explain的结果预估的rows值跟实际情况差距比较大

mysql> select * from t where (a between 1 and 1000)  and (b between 50000 and 100000) order by b limit 1;

其中a和b的索引结构:

如果扫描索引a进行查询,那么扫描索引a的前1000个值,然后取到对应的id,再到主键索引上查出每一行,然后根据字段b进行过滤,显然需要扫描1000行

如果使用索引b进行查询,那么扫描索引b的最后50001个值,与上面的执行过程相同,也是需要回到主键索引上取值再判断,索引需要扫描50001行

结论:1、扫描行数的估计值依然不准确

2、MySQL又选错了索引

索引选择异常处理:

1、采用force index 强行选择一个索引,mysql根据词法解析的结果分析可能可以使用的索引作为候选项,然后在候选项中依次判断每个索引需要扫描多少行,如果force index 指定的索引在候选项列表中,就直接选择。

缺点:1>这么写语句不优美  2>如果索引改了名字,这个语句也得改,显得麻烦,而且如果以后迁移到别的数据库,这个语法还可能会不兼容,主要解决是变更的及时性,因为选错索引的情况还是比较少出现的,索引开发通常不会写这个。如果线上出现问题的时候才去修改SQL语句,加上这个

2、考虑修改语句,引导MySQL使用我们期望的索引:比如把order by b limit 改成 order by b,a limit 1,语义逻辑是相同的

之前选择器是使用索引b,是因为它认为使用索引b可以避免排序(b本身是索引,已经有序,如果选择b,不需要再排序,只需要遍历),所以即使扫描行数多,也判定为代价最小

现在 order by b, a这个写法要求按照b,a排序,意味着两个索引都需要排序,因此,扫描行数成了影响决策的主要条件,所以选择只扫描1000行的索引

3、第三种方法是,在有些厂家下,我们新建出一个更合适的索引,来提供优化器做选择,或者删除误用的索引

读书笔记:Mysql实战45讲 (1-10讲)相关推荐

  1. MySQL实战45讲学习笔记

    文章目录 MySQL实战45讲-学习笔记 01 基础架构:一条SQL查询语句是如何执行的? mysql逻辑架构 连接器 查询缓存 分析器 优化器 执行器 02 日志系统:一条SQL更新语句如何执行 r ...

  2. 《MySQL实战45讲》——学习笔记04-05 “深入浅出索引、最左前缀原则、索引下推优化“

    04 | 深入浅出索引(上) 1. 什么是索引? 索引的出现其实就是为了提高数据查询的效率,就像书的目录一样,书有500页,每页存的都是书的内容,目录可能只有5页,只存了页码:通过目录能快速找到某个主 ...

  3. 《MySQL实战45讲》——学习笔记12 “InnoDB刷脏页的控制策略“

    本篇介绍MYSQL InnoDB的WAL机制带来的小问题--利用WAL技术,数据库将随机写转换成了顺序写,大大提升了数据库的性能,但也带来了内存脏页的问题: 脏页会被后台线程自动flush,也会由于数 ...

  4. 《MySQL实战45讲》——学习笔记01-03 “MySQL基本架构、日志系统、事务隔离“

    最近有新闻说"丁奇"炒股失败欠债,赶紧去极客时间买了他的<MySQL 实战 45 讲>以防下架,顺带重新系统的复习下MYSQL相关知识,记录下学习笔记: 本篇介绍: M ...

  5. MySQL 实战45讲--笔记

    文章目录 MySQL 实战45讲-->笔记 开篇词 基础篇(8讲) 01 | 基础架构:一条SQL查询语句是如何执行的? 1.1 SQL 语句在 MySQL 的各个功能模块中的执行过程. 依次看 ...

  6. 《Mysql实战45讲》学习笔记 1-22

    Mysql <Mysql实战45讲> 1.一条sql查询语句是如何执行的 Server层: 连接器,查询缓存,分析器,优化器,执行器 存储引擎层: 负责数据的存储和提取 (Innodb, ...

  7. 丁奇的MySQL实战45讲 学习笔记[链接]

    收录一下, 方便自己查阅 <MySQL实战45讲>1~15讲 -丁奇,学习笔记 <MySQL实战45讲>16~30讲 -丁奇,学习笔记 <MySQL实战45讲>31 ...

  8. mysql 实战 45讲 学习笔记 基础知识 原理剖析

    MySQL 实战45讲 持续更新中~ 00讲 开篇 我们知道如何写出逻辑正确的SQL语句来实现业务目标,却不确定这个语句是不是最优的 我们听说了一些使用数据库的最佳实践,但是更想了解为什么这么做 我们 ...

  9. MySQL实战45讲学习笔记----查询结果返回过程分析

    全表扫描时,客户端查询服务端数据库中大量数据,查询结果是如何返回给客户端的. 全表扫描对server层的影响 mysql -h$host -P$port -u$user -p$pwd -e " ...

  10. mysql实战45讲(15-22)

    mysql实战45讲学习笔记 15 日志与索引的关系 15.1 日志 1,分析一下在两阶段提交的不同时刻,MySQL 异常重启会出现什么现象. 如果在图中时刻 A 的地方,也就是写入 redo log ...

最新文章

  1. 独家 | 盘点9个适用所有学科的R数据可视化包(附链接)
  2. SSM-SpringMVC-16:SpringMVC中小论注解式开发之访问方式篇
  3. 红帽Linux 6.5上配置ASM流程
  4. hdu4848 DFS 暴搜+ 强剪枝
  5. Libusb交叉编译和移植
  6. 点击出现黑色背景的解决
  7. SpaceX载人龙飞船两名宇航员成功进入国际空间站
  8. linux下使用命令行分区、格式化文件系统、更新卷标名称
  9. K8S专题-基础组件的部署1
  10. NYOJ31 - 5个数求最值
  11. 数据结构之 普利姆算法总结
  12. Spring Cloud Alibaba#01.开篇立题
  13. python抢票软件源代码_自己写的一个抢票加速的Python小程序源码分享-----纯属娱乐...
  14. 微信手环1年多了,前主管终于出来聊了聊它是怎么诞生的
  15. Spring Boot 集成 批处理框架Spring batch
  16. linux判断季末日期,C#根据当前时间确定日期范围(本周、本月、本季度、本年度)...
  17. 挖个冰块就能修自己,科学家用「冰」做了辆科考机器车,南极火星都能跑
  18. unittest使用详解
  19. 强化区域产业链,优化区域产业布局,促区域经济高速发展
  20. 鸿蒙遗石是什么意思,《走近》:一方歙砚,这是徽州千年遗石,也是一生匠心...

热门文章

  1. 【云速建站】后台数据批量导入导出
  2. 遭遇“windows已经阻止此软件因为无法验证发行者”
  3. Can't connect to MySQL server on 'XXXX' (10055) 解决方案
  4. 自学数据结构_五月十日_综述
  5. 8月近况——少吃饭,多想事(总结)
  6. 操作系统原理——(1)引言:计算机系统和操作系统概述
  7. C++实现简单Kmeans聚类算法
  8. iOS开发中的好工具
  9. tx2+opencv源码编译教程(tx2+opencv4.4.0+opencv_contrib-4.4.0)
  10. 框架模式MVC与MVP在Android中的应用