读书笔记:Mysql实战45讲 取自极客时间

 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选择执行计划

执行器: 

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

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

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

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

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

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

               

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

 有了redo log,innodb就可以保证即使数据库发送异常冲去,之前提交的记录都不会丢失,这个能力成为 crash-safe

 日志模块: 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 是物理日志,记录de“在某个数据页做了什么修改”,是循环写的,空间固定会用完;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异常重启之后数据不丢失

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


  事务时保证一组数据库操作,要么全部成功,要么全部失败,在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这个表中查询长事务

深入浅出索引(上)


 

猜你喜欢

转载自blog.csdn.net/ligupeng7929/article/details/88852878