美文网首页
为什么99%的程序员都做不好SQL优化?

为什么99%的程序员都做不好SQL优化?

作者: 博学谷狂野架构师 | 来源:发表于2023-03-06 14:23 被阅读0次
    file
    1. 连接层

    最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程 池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务 器也会为安全接入的每个客户端验证它所具有的操作权限。

    1. 服务层

    第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部 分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。在该层,服务器会解 析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等, 最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大, 这样在解决大量读操作的环境中能够很好的提升系统的性能。

    1. 引擎层

    存储引擎层, 存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通 信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库 中的索引是在存储引擎层实现的。

    1. 存储层

    数据存储层, 主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询 日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。

    和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要 体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。 这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

    存储引擎介绍

    file

    大家可能没有听说过存储引擎,但是一定听过引擎这个词,引擎就是发动机,是一个机器的核心组件。 比如,对于舰载机、直升机、火箭来说,他们都有各自的引擎,是他们最为核心的组件。而我们在选择 引擎的时候,需要在合适的场景,选择合适的存储引擎,就像在直升机上,我们不能选择舰载机的引擎 一样。 而对于存储引擎,也是一样,他是mysql数据库的核心,我们也需要在合适的场景选择合适的存储引 擎。接下来就来介绍一下存储引擎。 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是 基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果 没有指定将自动选择默认的存储引擎。

    1. 建表时指定存储引擎
    CREATE TABLE 表名(
        字段1 字段1类型 [ COMMENT 字段1注释 ] ,
        ......
        字段n 字段n类型 [COMMENT 字段n注释 ]
    ) ENGINE = INNODB [ COMMENT 表注释 ] ;
    
    1. 查询当前数据库支持的存储引擎
    SHOW ENGINES;
    
    file
    • 创建表 my_myisam , 并指定MyISAM存储引擎
    CREATE TABLE my_myisam(
        `id` INT,
        `name` VARCHAR(10)
        )ENGINE = MYISAM;
    
    • 创建表 my_memory , 指定Memory存储引擎
    CREATE TABLE my_memory(
        `id` INT,
        `name` VARCHAR(10)
        )ENGINE = MEMORY;
    

    存储引擎特点

    上面我们介绍了什么是存储引擎,以及如何在建表时如何指定存储引擎,接下来我们就来介绍下来上面 重点提到的三种存储引擎 InnoDB、MyISAM、Memory的特点。

    InnoDB

    1. 介绍

    InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB是默认的 MySQL 存储引擎。

    1. 特点
    • DML操作遵循ACID模型,支持事务
    • 行级锁,提高并发访问性能;
    • 支持外键FOREIGN KEY约束,保证数据的完整性和正确性;
    1. 文件

    xxx.ibd:xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结 构(frm-早期的 、sdi-新版的)、数据和索引。

    参数:innodb_file_per_table

    show variables like 'innodb_file_per_table';
    
    Variable_name Value
    innodb_file_per_table ON

    如果该参数开启,代表对于InnoDB引擎的表,每一张表都对应一个ibd文件。 我们直接打开MySQL的 数据存放目录: D:\DevelopTools\mysql-5.7.19-winx64\data , 这个目录下有很多文件 夹,不同的文件夹代表不同的数据库,我们直接打开frx_db02文件夹。

    file

    可以看到里面有很多的ibd文件,每一个ibd文件就对应一张表,比如:我们有一张表 account,就有这样的一个account.ibd文件,而在这个ibd文件中不仅存放表结构、数据,还会存放该表对应的索引信息。 而该文件是基于二进制存储的,不能直接基于记事本打开,我们可以使用mysql提供的一个指令 ibd2sdi ,通过该指令就可以从ibd文件中提取sdi信息,而sdi数据字典信息中就包含该表的表结构。

    ibd2sdi account.ibd
    

    针对MySQL8有效

    1. 逻辑存储结构
    file
    • 表空间 : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以包含多个Segment段。
    • 段 : 表空间是由各个段组成的, 常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
    • 区 : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为16K, 即一个区中一共有64个连续的页。
    • 页 : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
    • 行 : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时所指定的字段以外,还包含两个隐藏字段(后面会详细介绍)。

    MyISAM

    1. 介绍

    MyISAM是MySQL早期的默认存储引擎。

    1. 特点

    不支持事务,不支持外键

    支持表锁,不支持行锁

    访问速度快

    1. 文件

    xxx.sdi:存储表结构信息

    xxx.MYD: 存储数据

    xxx.MYI: 存储索引

    Memory

    1. 介绍

    Memory引擎的表数据时存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为 临时表或缓存使用。

    1. 特点

    内存存放

    hash索引(默认)

    1. 文件

    xxx.sdi:存储表结构信息

    区别及特点

    特点 InnoDB MyISAM Memory
    存储限制 64TB
    事务安全 支持 - -
    锁机制 行锁 表锁 表锁
    B+tree索引 支持 支持 支持
    Hash索引 - - 支持
    全文索引 支持(5.6版本之后) 支持 -
    空间使用 N/A
    内存使用 中等
    批量插入速度
    支持外键 支持 - -

    存储引擎选择

    在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据 实际情况选择多种存储引擎进行组合。

    • InnoDB: 是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要 求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操 作,那么InnoDB存储引擎是比较合适的选择。
    • MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完 整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
    • MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是 对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。

    MySQL InnoDB引擎

    逻辑存储引擎

    InnoDB的逻辑存储结构如下图所示:


    file
    1. 表空间

    表空间是InnoDB存储引擎逻辑结构的最高层, 如果用户启用了参数 innodb_file_per_table(在8.0版本中默认开启) ,则每张表都会有一个表空间(xxx.ibd),一个mysql实例可以对应多个表空间,用于存储记录、索引等数据。

    段,分为数据段(Leaf node segment)、索引段(Non-leaf node segment)、回滚段(Rollback segment),InnoDB是索引组织表,数据段就是B+树的叶子节点, 索引段即为B+树的非叶子节点。段用来管理多个Extent(区)。

    区,表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为16K, 即一个区中一共有64个连续的页。

    页,是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。

    行,InnoDB 存储引擎数据是按行进行存放的。

    在行中,默认有两个隐藏字段:

    • Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
    • Roll_pointer:每次对某条引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

    架构

    概述

    MySQL5.5 版本开始,默认使用InnoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。

    file

    内存架构

    file

    在左侧的内存结构中,主要分为这么四大块儿: Buffer Pool、Change Buffer、Adaptive Hash Index、Log Buffer。 下来介绍一下这四个部分。

    1. Buffer Pool

    InnoDB存储引擎基于磁盘文件存储,访问物理硬盘和在内存中进行访问,速度相差很大,为了尽可能弥补这两者之间的I/O效率的差值,就需要把经常使用的数据加载到缓冲池中,避免每次访问都进行磁盘I/O。

    在InnoDB的缓冲池中不仅缓存了索引页和数据页,还包含了undo页、插入缓存、自适应哈希索引以及InnoDB的锁信息等等。

    缓冲池 Buffer Pool,是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增 删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频 率刷新到磁盘,从而减少磁盘IO,加快处理速度。

    缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:

    • free page:空闲page,未被使用。
    • clean page:被使用page,数据没有被修改过。
    • dirty page:脏页,被使用page,数据被修改过,也中数据与磁盘的数据产生了不一致。

    在专用服务器上,通常将多达80%的物理内存分配给缓冲池 。参数设置: show variables like 'innodb_buffer_pool_size';

    mysql> show variables like 'innodb_buffer_pool_size';
    +-------------------------+-----------+
    | Variable_name           | Value     |
    +-------------------------+-----------+
    | innodb_buffer_pool_size | 134217728 |
    +-------------------------+-----------+
    1 row in set (0.00 sec)
    
    1. Change Buffer

    Change Buffer,更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区 Change Buffer中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中。

    Change Buffer的意义是什么呢?

    先来看一幅图,这个是二级索引的结构图:

    file

    与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了ChangeBuffer之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。

    1. Adaptive Hash Index

    自适应hash索引,用于优化对Buffer Pool数据的查询。MySQL的innoDB引擎中虽然没有直接支持hash索引,但是给我们提供了一个功能就是这个自适应hash索引。因为前面我们讲到过,hash索引在进行等值匹配时,一般性能是要高于B+树的,因为hash索引一般只需要一次IO即可,而B+树,可能需要几次匹配,所以hash索引的效率要高,但是hash索引又不适合做范围查询、模糊匹配等。

    InnoDB存储引擎会监控对表上各索引页的查询,如果观察到在特定的条件下hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。

    自适应哈希索引,无需人工干预,是系统根据情况自动完成

    参数: adaptive_hash_index

    1. Log Buffer

    Log Buffer:日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log 、undo log),默认大小为 16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 I/O。

    参数:

    innodb_log_buffer_size:缓冲区大小

    innodb_flush_log_at_trx_commit:日志刷新到磁盘时机,取值主要包含以下三个:

    1:日志在每次事务提交时写入并刷新到磁盘,默认值。

    0:每秒将日志写入并刷新到磁盘一次。

    2:日志在每次事务提交后写入,并每秒刷新到磁盘一次。

    mysql> show variables like 'innodb_flush_log_at_trx_commit';
    +--------------------------------+-------+
    | Variable_name                  | Value |
    +--------------------------------+-------+
    | innodb_flush_log_at_trx_commit | 1     |
    +--------------------------------+-------+
    1 row in set (0.00 sec)
    

    磁盘结构

    接下来,再来看看InnoDB体系结构的右边部分,也就是磁盘结构:

    file
    1. System Tablespace

    系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建的,它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等)

    参数:innodb_data_file_path

    mysql> show variables like 'innodb_data_file_path';
    +-----------------------+------------------------+
    | Variable_name         | Value                  |
    +-----------------------+------------------------+
    | innodb_data_file_path | ibdata1:12M:autoextend |
    +-----------------------+------------------------+
    1 row in set (0.00 sec)
    

    系统表空间,默认的文件名叫 ibdata1。

    1. File-Per-Table Tablespaces

    如果开启了innodb_file_per_table开关 ,则每个表的文件表空间包含单个InnoDB表的数据和索引 ,并存储在文件系统上的单个数据文件中。

    开关参数:innodb_file_per_table,该参数默认开启。

    mysql> show variables like 'innodb_file_per_table';
    +-----------------------+-------+
    | Variable_name         | Value |
    +-----------------------+-------+
    | innodb_file_per_table | ON    |
    +-----------------------+-------+
    1 row in set (0.00 sec)
    

    那也就是说,我们每创建一个表,都会产生一个表空间文件,如图:

    file
    1. General Tablespaces

    通用表空间,需要通过 CREATE TABLESPACE 语法创建通用表空间,在创建表时,可以指定该表空间。

    A. 创建表空间

    CREATE TABLESPACE ts_name ADD DATAFILE 'file_name' ENGINE = engine_name;
    
    mysql> CREATE TABLESPACE ts_itheima ADD DATAFILE 'myitheima.ibd' ENGINE = innodb;
    Query OK, 0 rows affected (0.00 sec)
    

    B. 创建表时指定表空间

    CREATE TABLE xxx ... TABLESPACE ts_name;
    
    mysql> create table a(id int primary key auto_increment,name varchar(10)) engine=innodb tablespace ts_itheima;
    Query OK, 0 rows affected (0.01 sec)
    
    1. Undo Tablespaces

    撤销表空间,MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16M),用于存储 undo log日志。

    1. Temporary Tablespaces

    InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。

    1. Doublewrite Buffer Files

    双写缓冲区,innoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。

    1. Redo Log

    重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时,发生错误时, 进行数据恢复使用。

    以循环方式写入重做日志文件,涉及两个文件:

    -rw-r-----. 1 mysql mysql  50331648 10月  2 22:52 ib_logfile0
    -rw-r-----. 1 mysql mysql  50331648 10月  2 22:52 ib_logfile1
    

    前面我们介绍了InnoDB的内存结构,以及磁盘结构,那么内存中我们所更新的数据,又是如何到磁盘中的呢? 此时,就涉及到一组后台线程,接下来,就来介绍一些InnoDB中涉及到的后台线程。

    file

    后台线程

    file

    在InnoDB的后台线程中,分为4类,分别是:Master Thread 、IO Thread、Purge Thread、Page Cleaner Thread。

    1. Master Thread

    核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中, 保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收 。

    1. IO Thread

    在InnoDB存储引擎中大量使用了AIO来处理IO请求, 这样可以极大地提高数据库的性能,而IOThread主要负责这些IO请求的回调。

    线程类型 默认个数 职责
    Read thread 4 负责读操作
    Write thread 4 负责写操作
    Log thread 1 负责将日志缓冲区刷新到磁盘
    Insert buffer thread 1 负责将写缓冲区内容刷新到磁盘

    我们可以通过以下的这条指令,查看到InnoDB的状态信息,其中就包含IO Thread信息。

    show engine innodb status;
    
    file
    1. Purge Thread

    主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收。

    1. Page Cleaner Thread

    协助 Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻塞

    本文由传智教育博学谷狂野架构师教研团队发布。

    如果本文对您有帮助,欢迎关注点赞;如果您有任何建议也可留言评论私信,您的支持是我坚持创作的动力。

    转载请注明出处!

    相关文章

      网友评论

          本文标题:为什么99%的程序员都做不好SQL优化?

          本文链接:https://www.haomeiwen.com/subject/mjymldtx.html