深入研究MYSQL

深入研究MYSQL

MYSQL的架构

MYSQL架构

客户端

  1. 用来建立连接,提交SQL,项数据库发送请求

服务(server)

  1. 连接器:管理连接和验证权限,控制用户的连接
  2. 分析器:进行词法分析和语法分析,将sql语句转化成一个抽象语法树(AST),分析的插件(antlr,apache calcite)
  3. 优化器:基于规则的优化(RBO),基于成本的优化(CBO),优化sql语法的执行,规定执行流程
  4. 执行器:执行sql与存储引擎交互

存储引擎

  1. 不同的数据文件在磁盘的不同存在形式,不同的存储引擎数据文件和索引文件存放的位置不同
  2. 聚簇索引:数据和索引放在一起,.frm存放的是表结构,.ibd存放的是数据文件和索引文件(innodb)
  3. 非聚簇索引:数据和索引分开存放,.frm存放的是表结构,.MYI存放索引数据,.MYD存放实际数据

MYSQL的索引

索引的结构

  1. 哈希表:哈希表存储是无序的,当需要进行范围查询的时候,只能挨个遍历进行对比,效率极低(memory存储引擎支持,innodb存储引擎支持自适应hash)
  2. 二叉树:三层只能存七条数据,存更多的数据,深度越深,io次数越多,影响读取的速度
  3. B数:每个节点都有key和data,若data较大,则存储key的数量变小,存放的数据越多,深度越深,影响查询的性能

B+树

B+树

  • 一个m阶的B+树特点
    1. 根节点只有一个,分支数量为[2,m]
    2. 除跟以外的非叶子结点,每个节点包含的分支数范围[[m/2],m],其中[m/2]表示取大于m/2的最小整数
    3. 所有非叶子结点的关键字数目等于他的分支数量
    4. 所有的叶子结点都在同一层,且关键字数目范围是[[m/2],m],其中[m/2]表示取大于m/2的最小整数
    5. 所有的非叶子结点存放的都是索引
    6. 叶子结点包含全部关键字的信息,且叶子结点中的关键字依照大小顺序链接(链式环结构,如上图)
  • B+数作为索引的优势
    1. B+数每个节点可以存放更多的节点,降低了树的高度,将数据范围变为多个区间,区间越多,数据的检索速度就越快
    2. 非叶子结点只存key,叶子结点存放所有的key和data,可存更多的数据
    3. 叶子结点两两指针相互连接(符合磁盘的预读特性),顺序查询的效率更高

二级索引和辅助索引

  1. 一个表可以有n个索引,每个索引都是一个独立的B+树
  2. 表中的数据只存储一份,其他的非聚簇索引的叶子结点存放柜的是聚簇索引的key值,索引innodb中也包含了非聚簇索引
  3. 例子:id name age 四个列,id是主键,name是普通索引,数据是和id帮绑定的,name对应B+树的叶子结点中存储的是id,此时name就叫做二级索引或辅助索引

联合索引和复合索引

  1. 类似于联合主键的意思,将多个列共同组成一个索引,称之为联合索引

名词解释

  • 回表
    • id(主键) name(普通索引) age gender
    • select * from table where name = ‘zhangsan’;
    • 查找过程:先根据name的值去name的B+树的叶子结点中取出id的值,再根据id查找结果
    • 回表的效率较低,尽可能避免
  • 索引覆盖
    • id(主键) name(普通索引) age gender
    • select id,name from table where name = ‘zhangsan’;
    • 查找过程:先根据name的值去name的B+树查找结果,可以直接获取到id和name,不需要去id的B+树中查找数据了
    • 索引的叶子结点包含了查询的全部数据,叫做索引覆盖,推荐使用
  • 最左匹配原则
    • id(主键) name和 age是组合索引 gender 查询的时候必须要从左向右匹配
    • select * from table where name =? and age = ?
    • select * from table where age=?(不符合)
    • select * from table where name=?
    • select * from table where age =? and name= ?(优化器会优化)
  • 索引下推
    • id(主键) name和 age是组合索引 gender 查询的时候必须要从左向右匹配
    • select * from table where name =? and age = ?
    • 没有索引下推之前:先根据name的值从存储过程中拿到符合条件的数据,然后在server中对age进行过滤。
    • 有索引下推后:直接根据name和age从存储引擎中国筛选对应的数据,然后返回server,不需要做数据过滤

事务

事务的特点(ACID)

  1. 原子性:整个事务所有的操作,要么全部完成,要么全部不完成,不可能停滞在中间的某一个环节。 事务在执行过程中发生错误,会被回滚(Rollback)到事务开始之前的状态(undolog)
  2. 一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。(一致性是我们追求的根本,一致性的实现是由其他三个特点来保证的)
  3. 隔离性:隔离状态执行事务,使他们好像是在系统在给定时间内执行的唯一操作(MVCC锁)
  4. 持久性:在事务完成以后,该事务对数据库所做的更改便持久的保存在数据库中,并不会回滚(redolog)

mysql中的日志

  1. binlog二进制日志:用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步,用于数据库的基于时间点的还原(数据的同步及恢复,位于server层)
  2. undolog回滚日志:保证数据的原子性,保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读(undolog中存放的数据是一个个链表,链首是最新的旧记录,链尾是最旧的数据)
  3. redolog重做日志:确保事务的持久性。redo日志记录事务执行后的状态,用来恢复未写入data file的已成功事务更新的数据。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
  4. errorlog错误日志:错误日志记录着mysqld启动和停止,以及服务器在运行过程中发生的错误的相关信息。
  5. slowlog慢日志:慢日志记录执行时间过长和没有使用索引的查询语句,报错select、update、delete以及insert语句,慢日志只会记录执行成功的语句
  6. relaylog中继日志
  7. binlog和redolog都会同时记录数据,他们的记录数据的顺序是怎样的?二阶段提交,执行器先从引擎中找到数据,如果在内存中直接返回,如果不在内存中,查询后返回,只想起拿到数据后会先修改数据,然后调用引擎接口重新写入数据,引擎将数据更新到内存,同时写数据到redo中,此时处于prepare阶段,并通过执行器执行完成,随时可操作,只想起生成这个操作的binlog,执行器调用引擎的事务提交接口,引擎把刚刚写完的redo改成commit状态,更新完成

MVCC多版本并发控制

并发情况

  1. 读读:不存在任何问题,不需要并发控制
  2. 读写:有数据安全问题,脏读、幻读、不可重复读
  3. 写写:有数据安全问题,可能存在更新丢失问题

隐藏字段

  1. DB_TRX_ID:创建这条记录或者最后一次修改该记录的事务id(在事务操作的时候,事务的id是递增的)
  2. DB_ROLL_PTR:回滚指针,指向数据的上一个版本
  3. DB_ROE_ID:隐藏主键,如果没有显示主键,就会多一个隐藏主键

readview(事务进行快照读操作的时候产生的读视图)

  1. trx_list:readview生成时刻当前系统活跃的事务id
  2. up_limit_id:活跃列表中最小的事务id值
  3. low_limit_id:系统尚未分配的下一个事务id的值
  4. 可见性算法:生成的readview会个根据可见性算法来判断是否可以读取到对应的数据结果
    可见性算法
  5. 总结:在RC隔离级别里,每次进行快照读操作的时候都会重新生成readview,所以每次可以查询到最新的结果记录,在RR隔离级别里,只有当前事务在第一次进行快照读的时候才会生成readview,之后进行的快照读操作都会沿用之前的readview

事务并发的问题

  1. 脏读:指在一个事务处理的过程里,读取了另一个未提交的事务中的数据
  2. 不可重复读:指在对于数据库中的某个数据,一个事务范围内多次查询,却返回不同的数据值,这是由于在查询的间隔中被另一个事务修改并提交了。
  3. 不可重复读和脏读的区别是:脏读是某一事务读取了另一个事务未提交的脏数据,不可重复读是读取了前一事务提交的数据
  4. 幻读:一个事务在前后查询同一个范围的时候,后一次查询看到了前一次查询没看到的行,如果事务中都使用快照读,那么就不会产生幻读现象,但是快照读和当前读混用就会产生幻读现象(加锁解决for update)
  5. 当前读:数据读取的时候读取的都是最新的版本数据(触发select lock in share mode,select for update,update,delete,insert)
  6. 快照读:读取数据的时候会根据一定规则读取事务可见版本的数据(可能是过期的数据)(触发select)

事务的隔离级别

  1. Read uncommitted (读未提交):最低级别,以上问题均无法解决。
  2. Read committed (读已提交):读已提交,可避免脏读情况发生。
  3. Repeatable Read(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。(mysql默认隔离级别
  4. Serializable (串行化):最严格的事务隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不可重复读、幻读情况的发生。

java面试相关问题

MySql的性能优化

回答

MySql的性能优化,我认为可以分为四个部分

  1. 第一个是硬件和操作系统层面的优化
    1. 从硬件层面上来说,影响mysql的性能因素主要是CPU,可用内存大小,磁盘读写速度,网络带宽
    2. 从操作系统层面来说,应用文件句柄数,操作系统的网络配置都会影响MySql的性能
    3. 这部分的优化一般是由DBA或者运维工程师来完成,在硬件基础资源的优化中,我们重点关注的是服务本身所承载的体量,然后提出合理的指标要求,避免出现资源浪费的一个现象
  2. 第二个是架构设计层面的优化
    1. MySql是一个磁盘IO访问非常频繁的关系型数据库,在高并发和高性能的场景中,MySql数据库必然会承受巨大的并发压力,在此时我们优化的方式主要可以分为几个部分。
    2. 第一个是搭建MySql主从集群,单个MySql服务容易导致单点故障,一旦服务宕机,将会导致依赖MySql数据库的应用全部无法响应,主从集群或者主主集群都可以去保证服务的高可用性。
    3. 第二个读写分离设计,在读多写少的场景中,通过读写分离的方案,可以去避免读写冲突导致的性能问题。
    4. 第三个是引入分库分表的机制,通过分库可以降低单个服务器节省的一个IO压力,通过分表的方式可以去降低单表数据量从个人去提升Sql的查询效率
    5. 第四点针对热点数据可以引入更为高效的分布数据库,比如Redis,MongoDB等,他们可以很好的缓解MySql的访问压力,同时还能够提升数据的检索性能。
  3. 第三个是程序配置的优化
    1. MySql是一个经过互联网大厂检验过的生产级别的成熟数据库,对于MySql数据库本身优化,一般可以通过MySql的配置文件my.cnf来完成,比如MySql5.7版本默认的最大连接数是151个,这个值可以在my.cnf中去修改
    2. 第二个binlog日志,默认是不开启,我们也可以通过修改my.cnf文件设置开启
    3. 第三个是缓存池Bufferpool,默认大小配置,而这些配置一般是和用户的安装环境以及使用场景有关系,因此这些配置官方只会提供一个默认的配置,具体的情况,还是得由使用者去根据实际情况去修改。
    4. 关于配置项的修改需要关注两点层面,第一个是配置的作用域,它可以分为会话级别和全局范围,第二个是是否支持热加载,因此针对这两个点,我们需要注意的是全局参数的设定,对于已经存在的会话是无法生效的,会话参数的设定,随着会话的销毁而失效,第三个是全局类的统一配置,建议配置在默认配置文件中,否则重启服务器会导致配置失效
  4. 第四个是SQL执行的优化
    1. 可以分为三个步骤
    2. 第一个,慢sql的定位和排查,我们可以通过慢查询日志和慢查询日志的工具来分析得到有问题的sql列表
    3. 第二个是执行计划分析,针对慢sql,我们可以使用该关键字explain来去查看当前sql的执行计划,可以重点关注type、key、rows、filterd等字段,从而去定位更改sql执行慢的根本原因,再去有的放矢的去进行优化
    4. 第三个是使用过show profile工具,show profile工具是MySql提供的可以用来分析当前会话中sql语句资源消耗情况的工具,可以用于sql调优的测量,在当前会话中默认情况下show profile是个关闭状态,打开之后会保存最近15次的运行结果,针对运行慢sql通过show profile工具进行详细分析,可以得到sql执行过程中,所有资源的开销情况,比如:IO开销、CPU开销、内存开销等等

常见sql优化的规则

  1. sql的查询一定要基于索引来进行数据扫描
  2. 避免索引列上使用函数或者运算符,会导致索引失效
  3. 模糊查询中where自居中like %好尽量放置在右边
  4. 尽可能使用过sql语句用到的索引完成排序,避免使用文件排序的方式
  5. 查询有效的列信息即可,少用*代替列信息
  6. 永远用小结果集去驱动大的结果集

猜你喜欢

转载自blog.csdn.net/ilvjiale/article/details/121406795