MySQL三万字精华面试汇总(收藏系列)

文章目录

      • 前言
    • 一、MySQL架构
      • 1. 连接层
      • 2. 服务层
      • 3. 引擎层
      • 4. 存储层
        • 架构图与查询流程
        • 存储引擎相关问题
    • 二、存储引擎
    • 1. 查看存储引擎
      • 2. 设置存储引擎
      • 3. 存储引擎对比
        • 文件存储结构对比
        • 特性对比
      • 4. 面试回答
        • InnoDB与MyISAM存储引擎的差异
        • 关于自增主键ID的问题
        • 关于`select count(*)`执行速度的问题
    • 三、数据类型
      • 1. CHAR和VARCHAR的区别
      • 2. 列的字符串类型
      • 3. BLOB和TEXT的区别
    • 四、索引
      • 1. 索引的理解
      • 2. 索引操作语法
      • 3. 索引的优势与劣势
      • 4. 索引分类
      • 5. 索引结构
      • 6. 其他索引类型
      • 7. 索引使用注意事项
    • 五、MySQL查询
      • 1. COUNT(*)、COUNT(1)和COUNT(列名)的区别
      • 2. IN和EXISTS的区别
      • 3. UNION和UNION ALL的区别
      • 4. SQL执行顺序
      • 5. 连接类型区别
    • 六、MySQL事务
      • 1. 事务概述
      • 2. 并发事务问题
      • 3. 事务隔离级别
      • 4. MVCC多版本并发控制
      • 5. 事务日志
      • 6. 事务的实现
      • 7. 事务日志的构成
      • 8.拓展:MySQL中的其他日志
      • 9.分布式事务相关
    • 七、MySQL锁机制
      • 1.锁的基本概念
      • 2.锁的分类
      • 3.不同存储引擎的锁机制
      • 4.加锁机制
      • 5.select for update的作用
      • 6.死锁问题
    • 八、MySQL调优
      • 1.影响mysql的性能因素
        • 业务需求对MySQL的影响(合适合度)
        • 存储定位对MySQL的影响
        • Schema设计对系统的性能影响
        • 硬件环境对系统性能的影响
      • 2.性能分析
        • MySQL Query Optimizer
        • MySQL常见瓶颈
        • 性能下降SQL慢 执行时间长 等待时间长 原因分析
        • MySQL常见性能分析手段
        • 性能瓶颈定位
        • Explain(执行计划)
        • 各字段解释
        • 慢查询日志
        • Show Profile 分析查询
        • 日常开发需要注意的结论
        • 查询中哪些情况不会使用索引?
      • 3.性能优化
        • 索引优化
        • 一般性建议
        • 查询优化
        • GROUP BY 关键字优化
        • 数据类型优化
      • 九、分区、分表、分库
        • 1.MySQL 分区
        • 2.分库与分表的设计
      • 十、主从复制
        • 1.复制的基本原理
        • 2.复制的基本原则
        • 3.复制的最大问题
      • 十一、其他问题
        • 1.说一说三个范式
        • 2.百万级别或以上的数据如何删除

前言

在准备面试时,不推荐一上来就罗列各种面试题然后死记硬背。这种方式对技术提升作用有限,在正经面试中,一旦面试官深入追问,就很容易露馅。

个人建议将面试题当作费曼学习法里回顾、简化知识的重要环节。准备面试时,针对每一道题,尝试自己给自己讲解。讲解过程中,留意自己的表现,判断是否满意。

要是发现讲解不顺畅或者存在知识盲点,那就针对这些问题继续深入学习。不断重复这个过程,随着对知识点理解的加深和掌握程度的提高,获得理想offer的机会也就越来越大。加油!

MySQL三万字精华面试汇总(收藏系列)_第1张图片

一、MySQL架构

与其他数据库不同,MySQL架构具有独特优势,能在多种场景中良好应用,其核心优势体现在插件式存储引擎架构上。该架构将查询处理、系统任务与数据存储提取分离,用户可根据业务需求灵活选择合适的存储引擎。
MySQL三万字精华面试汇总(收藏系列)_第2张图片

1. 连接层

连接层位于最上层,负责客户端连接服务。主要功能包括连接处理、授权认证及安全方案实施。此层引入线程池概念,为通过认证的客户端提供线程,并支持基于SSL的安全链接。同时,服务器会验证每个客户端的操作权限。

2. 服务层

服务层是第二层,承担大部分核心服务功能。涵盖查询解析、分析、优化、缓存,以及所有内置函数的处理。此外,所有跨存储引擎的功能,如触发器、存储过程、视图等,也在这一层实现。

3. 引擎层

引擎层为第三层,存储引擎负责MySQL中数据的存储和提取。服务器通过API与存储引擎通信,不同存储引擎功能各异,用户可按需选择。

4. 存储层

存储层是第四层,主要将数据存储在设备的文件系统上,并与存储引擎进行交互。

架构图与查询流程

有人会要求画出MySQL架构图,这确实有一定难度。另外,关于MySQL的查询流程,即一条SQL语句在MySQL中的执行过程如下:

客户端请求 → 连接器(验证用户身份并授予权限) → 查询缓存(若存在缓存则直接返回,否则执行后续操作) → 分析器(对SQL进行词法和语法分析) → 优化器(优化执行方案,选择最优方法) → 执行器(检查用户执行权限,若有则调用引擎接口) → 引擎层获取数据返回(若开启查询缓存则缓存查询结果)

MySQL三万字精华面试汇总(收藏系列)_第3张图片

存储引擎相关问题

常见问题如“说说MySQL有哪些存储引擎?它们有哪些区别?”下面将详细介绍存储引擎。

二、存储引擎

存储引擎是MySQL处理不同表类型SQL操作的组件。不同存储引擎提供不同的存储机制、索引技巧和锁定水平,用户可灵活选择,一个数据库中的多个表可使用不同引擎以满足性能和实际需求,合适的存储引擎能提升数据库整体性能。

MySQL采用可插拔的存储引擎体系结构,可在运行时加载或卸载存储引擎。

1. 查看存储引擎

-- 查看支持的存储引擎
SHOW ENGINES;

-- 查看默认存储引擎
SHOW VARIABLES LIKE 'storage_engine';

-- 查看具体某一个表所使用的存储引擎
SHOW CREATE TABLE tablename;
SHOW TABLE STATUS LIKE 'tablename';
SHOW TABLE STATUS FROM database WHERE name="tablename";

2. 设置存储引擎

-- 建表时指定存储引擎,默认是INNODB
CREATE TABLE t1 (i INT) ENGINE = INNODB;
CREATE TABLE t2 (i INT) ENGINE = CSV;
CREATE TABLE t3 (i INT) ENGINE = MEMORY;

-- 修改存储引擎
ALTER TABLE t ENGINE = InnoDB;

-- 修改默认存储引擎,也可在配置文件my.cnf中修改
SET default_storage_engine=NDBCLUSTER;

为防止所需引擎不可用导致意外,可启用NO_ENGINE_SUBSTITUTION SQL模式,若引擎不可用将产生错误而非警告,且不会创建或更改表。

3. 存储引擎对比

常见的存储引擎有InnoDB、MyISAM、Memory、NDB。InnoDB是MySQL默认存储引擎,支持事务、行级锁定和外键。

文件存储结构对比

在MySQL中创建数据表,数据目录对应的数据库目录下会有.frm文件,用于保存数据表的元数据,与存储引擎无关。可通过SHOW VARIABLES LIKE 'data%'查看MySQL数据存储位置。

  • MyISAM:物理文件包括.frm(存储元数据)、.MYD(存储数据)和.MYI(存储索引信息)。
  • InnoDB.frm文件存储元数据,数据存储在.ibd.ibdata文件中,存储方式可通过配置决定是共享表空间(使用.ibdata文件)还是独享表空间(使用.ibd文件)。
特性对比
对比项 MyISAM InnoDB
主外键 不支持 支持
事务 不支持 支持
行表锁 表锁,不适合高并发操作 行锁,适合高并发操作
缓存 只缓存索引 缓存索引和真实数据,对内存要求高
表空间
关注点 性能 事务
默认安装

4. 面试回答

在面试中回答这类问题,关键要清晰、有条理地阐述各要点,突出重点内容和对比差异。以下是优化后的回答方式:

InnoDB与MyISAM存储引擎的差异
  1. 事务支持方面:InnoDB全面支持事务,这使得它在处理需要数据一致性和完整性保障的业务场景,如银行转账、电商交易等事务性操作时,表现出色。通过事务的原子性、一致性、隔离性和持久性(ACID)特性,确保一组相关操作要么全部成功提交,要么全部回滚,避免数据不一致问题。而MyISAM不支持事务,这在一些对数据一致性要求严格的场景下存在局限性。这也是MySQL将默认存储引擎从MyISAM变更为InnoDB的重要原因之一,因为现代应用程序大多需要可靠的事务处理能力。
  2. 外键支持方面:InnoDB支持外键约束,能够建立表与表之间的关联关系,保证数据的参照完整性。例如在一个数据库系统中,订单表和客户表可以通过外键关联,确保订单数据中的客户ID与客户表中的ID一致,防止出现孤立订单数据。而MyISAM不支持外键,如果尝试将一个包含外键的InnoDB表转换为MyISAM表,会导致转换失败。
  3. 索引结构方面
    • InnoDB采用聚簇索引,其数据文件和主键索引紧密结合,数据存储在主键索引的叶子节点上。这意味着InnoDB表必须定义主键,且通过主键进行查询时效率极高。但对于辅助索引,需要进行两次查询操作,首先通过辅助索引找到对应的主键值,然后再依据主键值查询到具体数据。所以,为了保证查询性能,主键不宜设置过大,因为主键过大,所有相关的辅助索引也会随之增大,占用更多存储空间且降低查询效率。
    • MyISAM则是非聚簇索引,其数据文件和索引文件是相互分离的。索引中保存的是指向数据文件的指针,无论是主键索引还是辅助索引都是独立存在的。这种结构使得MyISAM在某些场景下,如全表扫描操作,可能具有一定优势,但在基于索引的查询中,尤其是涉及到复杂关联查询时,可能不如InnoDB高效。
  4. 记录行数统计方面
    • InnoDB并不会专门保存表的具体行数。当执行select count(*) from table语句时,InnoDB需要对全表进行扫描,逐行统计数据行数,因此随着表数据量的增大,执行该语句的耗时会显著增加。
    • MyISAM则通过一个内部变量记录了整个表的行数。当执行相同的select count(*) from table语句时,MyISAM只需读取这个变量的值,就能快速返回结果,速度远远快于InnoDB。
  5. 锁粒度方面
    • InnoDB最小的锁粒度为行锁,这意味着在进行数据操作时,只会锁定涉及的具体行数据,对其他行的并发访问影响较小,非常适合高并发的读写场景。例如在一个电商系统中,多个用户同时对不同商品进行下单操作,行锁机制可以确保这些操作之间相互不干扰。
    • MyISAM最小的锁粒度是表锁,即使只是对表中的一条记录进行操作,也会锁住整个表。这就导致在高并发环境下,一个更新语句执行时,会阻塞其他所有对该表的查询和更新操作,大大限制了系统的并发访问能力。这也是MySQL将默认存储引擎变更为InnoDB的另一个关键原因。
  6. 缓存机制方面
    • MyISAM主要缓存索引数据,并不缓存实际的数据记录。这种缓存方式在一些以读操作频繁且数据量较大的场景下,能够有效减少磁盘I/O,但对于需要频繁访问数据本身的操作,可能会导致多次磁盘读取。
    • InnoDB不仅缓存索引,还会缓存实际的数据页。这使得InnoDB在处理读写混合的业务场景时具有优势,但同时也对服务器内存提出了更高要求,内存的大小对InnoDB的性能有着决定性影响。如果内存不足,可能会频繁出现磁盘I/O操作,导致性能大幅下降。
  7. 表空间占用方面:一般情况下,MyISAM的表空间占用相对较小,因为其数据文件和索引文件结构相对简单。而InnoDB由于采用聚簇索引以及需要存储更多的事务和数据一致性相关信息,表空间占用通常较大。
  8. 关注点方面:MyISAM更侧重于性能表现,在一些对事务和并发要求不高,但对查询速度有一定要求的场景下,如简单的日志记录、静态数据存储等,能发挥较好作用。而InnoDB则以事务处理为核心关注点,通过完善的事务机制和行锁等特性,满足了现代应用程序对数据一致性和高并发处理的需求。
  9. 默认设置方面:在当前的MySQL版本中,默认的存储引擎是InnoDB,这反映了数据库行业对于事务处理和高并发性能的重视趋势。
关于自增主键ID的问题

假设有一张表,其中包含ID自增主键。当插入了17条记录后,删除了第15、16、17条记录,然后重启MySQL,再次插入一条记录,此时该记录的ID值取决于表所使用的存储引擎:

  1. 如果表的类型是MyISAM:新插入记录的ID将是18。这是因为MyISAM表会将自增主键的最大ID值持久化存储在数据文件中。即使MySQL重启,这个最大ID值也不会丢失,新插入记录的ID会基于之前保存的最大ID值继续递增。
  2. 如果表的类型是InnoDB:新插入记录的ID将是15。InnoDB表只是将自增主键的最大ID临时存储在内存中。当数据库重启或者对表执行OPTION操作时,内存中的最大ID值会丢失,InnoDB会重新扫描表数据来确定当前的最大ID值,由于之前删除了15、16、17条记录,此时扫描得到的最大ID为14,新插入记录的ID会从15开始递增。
关于select count(*)执行速度的问题

在执行select count(*)语句时,MyISAM通常比InnoDB更快,原因如下:

  1. MyISAM的实现方式:MyISAM存储引擎在内部维护了一个计数器,用于记录表中的总行数。当执行select count(*) from t这样的语句时,MyISAM无需对表数据进行扫描,直接读取这个预先存储的计数器值,就能快速返回结果。这种方式在数据量较大的表上,优势尤为明显,能够极大地提高查询效率。
  2. InnoDB的实现方式:InnoDB存储引擎与MyISAM不同,它没有在磁盘上专门存储表的总行数。当执行select count(*) from t语句时,InnoDB需要将表中的数据逐行读取出来,通过一行一行的累加统计,最终得出总数量并返回。随着表中数据量的不断增大,这种全表扫描的方式会导致查询耗时越来越长。InnoDB之所以不像MyISAM那样将总行数存储在磁盘上,主要与它的事务特性相关。由于InnoDB采用多版本并发控制(MVCC)机制,在同一时刻,不同事务可能看到的数据版本不同,这就使得“应该返回多少行”这个问题在某些情况下变得不确定,因此无法简单地通过存储一个固定的总行数来满足所有查询需求。

三、数据类型

MySQL主要数据类型分为以下五大类:

  1. 整数类型:BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、INT、BIG INT
  2. 浮点数类型:FLOAT、DOUBLE、DECIMAL
  3. 字符串类型:CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB
  4. 日期类型:Date、DateTime、TimeStamp、Time、Year
  5. 其他数据类型:BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection等
    MySQL三万字精华面试汇总(收藏系列)_第4张图片
    MySQL三万字精华面试汇总(收藏系列)_第5张图片
    MySQL三万字精华面试汇总(收藏系列)_第6张图片

1. CHAR和VARCHAR的区别

  • 相同点char(n)varchar(n)中的n都代表字符个数,超过最大长度n字符串会被截断。
  • 不同点char是固定长度,不管实际存储数据长度,按规定长度分配存储空间,会截断尾部空格,存储上限为255字节,适合存储短的、固定长度字符串;varchar长度可变,根据实际存储数据分配空间,保存数据时会额外用1或2个字节记录长度,不会截断尾部空格。

2. 列的字符串类型

列的字符串类型包括SET、BLOB、ENUM、CHAR、TEXT、VARCHAR。

3. BLOB和TEXT的区别

BLOB是二进制对象,可容纳可变数量的数据,有TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB四种类型;TEXT是不区分大小写的BLOB,有TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT四种类型。BLOB保存二进制数据,TEXT保存字符数据。

四、索引

1. 索引的理解

MySQL官方定义索引是帮助高效获取数据的数据结构,本质是数据结构。索引目的是提高查询效率,可类比字典、车次表、图书目录等。可简单理解为“排好序的快速查找数据结构”,数据库在数据本身之外维护特定查找算法的数据结构,通过引用数据实现高级查找。索引通常以文件形式存储在磁盘上,常见的索引结构是B+树,此外还有哈希索引等。
MySQL三万字精华面试汇总(收藏系列)_第7张图片

2. 索引操作语法

-- 创建索引
CREATE [UNIQUE] INDEX indexName ON mytable(username(length));
-- 修改表结构添加索引
ALTER table tableName ADD [UNIQUE] INDEX  indexName(columnName);

-- 删除索引
DROP INDEX [indexName] ON mytable;

-- 查看索引
SHOW INDEX FROM table_name\G;

-- 使用ALERT命令添加索引
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list);
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list);
ALTER TABLE tbl_name ADD INDEX index_name (column_list);
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list);

3. 索引的优势与劣势

  • 优势:提高数据检索效率,降低数据库IO成本;降低数据排序成本,减少CPU消耗。
  • 劣势:占用内存,因为索引也是一张表,保存主键和索引字段并指向实体表记录;降低更新表的速度,更新表时需同时更新索引文件。

4. 索引分类

  • 数据结构角度:B+树索引、Hash索引、Full-Text全文索引、R-Tree索引。
  • 物理存储角度:聚集索引(clustered index)和非聚集索引(non-clustered index,也叫辅助索引secondary index),二者均为B+树结构。
  • 逻辑角度:主键索引(不允许有空值)、普通索引或单列索引(每个索引包含单个列)、多列索引(复合索引、联合索引,遵循最左前缀集合)、唯一索引或非唯一索引、空间索引(对空间数据类型字段建立,只能在MyISAM表中创建)。

5. 索引结构

索引在存储引擎层面实现,不同存储引擎对索引类型的支持和实现可能不同。

  • B+Tree索引:MyISAM和InnoDB存储引擎都使用B+Tree结构,与B-Tree相比,所有数据存放在叶子节点,叶子节点通过指针连接成链表,提高相邻数据检索效率。

    • B-Tree:为磁盘等外存储设备设计的平衡查找树,系统以磁盘块为单位读取数据,InnoDB以页为磁盘管理最小单位。B-Tree能高效定位数据所在磁盘块,每个节点可包含大量关键字和分支。
    • B+Tree:在B-Tree基础上优化,非叶子节点只存储键值信息,数据记录存放在叶子节点,可增加每个节点存储的键值数量,降低树的高度。B+Tree支持主键范围查找、分页查找和随机查找,具有最左匹配特性。
  • MyISAM主键索引与辅助索引:索引文件和数据文件分离,叶子节点存放数据记录地址,属于非聚簇索引。主索引和辅助索引区别不大,主键索引键值不能重复。详见见下图:
    MySQL三万字精华面试汇总(收藏系列)_第8张图片

  • InnoDB主键索引与辅助索引:数据文件本身就是主键索引文件,属于聚簇索引,一个表只能有一个聚簇索引。主键索引叶子节点存放完整数据记录,辅助索引叶子节点存放主键值,查询时需进行回表操作。

6. 其他索引类型

  • Hash索引:通过Hash算法将数据库字段数据转换为定长Hash值,与行指针存入Hash表。发生Hash碰撞时以链表形式存储。检索时再次计算Hash值取值,适用于等值查询,不支持区间查询和多列联合索引的最左匹配规则。
  • full-text全文索引:MyISAM和InnoDB(从MySQL 5.6版本开始支持)的特殊索引类型,用于全文索引,替代效率较低的LIKE模糊匹配操作。
  • R-Tree空间索引:MyISAM的特殊索引类型,用于地理空间数据类型。

7. 索引使用注意事项

  • 创建索引的情况:主键自动建立唯一索引;频繁作为查询条件的字段;查询中与其他表关联的字段;高并发下倾向创建组合索引;查询中排序的字段;查询中统计或分组字段。
  • 不创建索引的情况:表记录太少;经常增删改的表;数据重复且分布均匀的表字段;频繁更新的字段;where条件里用不到的字段。
  • 高效索引:覆盖索引指select的数据列可从索引中取得,无需读取数据行,可通过explain的extra列判断是否为索引覆盖查询。

五、MySQL查询

1. COUNT(*)、COUNT(1)和COUNT(列名)的区别

  • 执行效果:COUNT(*)和COUNT(1)统计时不忽略列值为NULL的情况,COUNT(列名)会忽略列值为NULL的计数。
  • 执行效率:列名为主键时,COUNT(列名)比COUNT(1)快;列名不是主键时,COUNT(1)比COUNT(列名)快;表有多个列且无主键时,COUNT(1)效率优于COUNT();有主键时,SELECT COUNT(主键)效率最优;表只有一个字段时,SELECT COUNT()最优。

2. IN和EXISTS的区别

  • EXISTS:对外表用loop逐条查询,每次查看EXISTS条件语句,若能返回记录行则条件为真,返回当前记录;否则丢弃。
  • IN:相当于多个OR条件叠加。
    若两表大小相当,INEXISTS差别不大;若一表小一表大,子查询表大时用EXISTS,子查询表小时用IN

3. UNION和UNION ALL的区别

二者都用于合并两个结果集,要求字段个数和类型一致。UNION会筛选掉重复数据并排序,效率较低;UNION ALL不筛选重复数据,只是简单合并结果。

4. SQL执行顺序

-- 手写顺序
SELECT DISTINCT <select_list>
FROM  <left_table> <join_type>
JOIN  <right_table> ON <join_condition>
WHERE  <where_condition>
GROUP BY  <group_by_list>
HAVING <having_condition>
ORDER BY <order_by_condition>
LIMIT <limit_number>

-- 机读顺序
FROM  <left_table>
ON <join_condition>
<join_type> JOIN  <right_table> 
WHERE  <where_condition>
GROUP BY  <group_by_list>
HAVING <having_condition>
SELECT
DISTINCT <select_list>
ORDER BY <order_by_condition>
LIMIT <limit_number>

MySQL三万字精华面试汇总(收藏系列)_第9张图片

5. 连接类型区别

常见问题如“mysql的内连接、左连接、右连接有什么区别?”以及“什么是内连接、外连接、交叉连接、笛卡尔积?”这里涉及不同连接类型的概念和应用。

六、MySQL事务

1. 事务概述

MySQL事务用于处理操作量大、复杂度高的数据。事务是一组SQL语句组成的逻辑处理单元,具有ACID属性:

  • 原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不完成,发生错误会回滚到事务开始前的状态。
  • 一致性(Consistency):事务开始和结束后,数据库的完整性约束不被破坏。
  • 隔离性(Isolation):一个事务的执行不受其他事务干扰。
  • 持久性(Durability):事务完成后,对数据库的更改持久保存。

2. 并发事务问题

并发事务处理可能带来以下问题:

  • 更新丢失(Lost Update):两个事务同时更新同一行数据,导致其中一个事务的更新丢失。
  • 脏读(Dirty Reads):一个事务读取另一个未提交事务的数据,若该事务回滚,读取的数据为脏数据。
  • 不可重复读(Non-Repeatable Reads):一个事务多次读取同一数据,期间另一个事务对数据进行更新并提交,导致多次读取结果不一致。
  • 幻读(Phantom Reads):一个事务读取几行数据后,另一个事务插入或删除数据,该事务再次查询时发现记录数发生变化。

3. 事务隔离级别

数据库事务隔离级别有4种,由低到高分别为:

  • READ - UNCOMMITTED(读未提交):允许读取未提交的数据变更,可能导致脏读、幻读或不可重复读。
  • READ - COMMITTED(读已提交):允许读取已提交的数据,可阻止脏读,但可能出现幻读或不可重复读。
  • REPEATABLE - READ(可重复读):同一字段多次读取结果一致,可阻止脏读和不可重复读,但可能出现幻读。MySQL InnoDB存储引擎默认隔离级别为可重复读,使用Next - Key Lock算法可避免幻读。
  • SERIALIZABLE(可串行化):最高隔离级别,事务依次逐个执行,可防止脏读、不可重复读和幻读,但并发性能差。

可通过SHOW VARIABLES LIKE ‘tx_isolation’(MySQL 8.0改为SELECT @@transaction_isolation;)查看当前数据库的事务隔离级别。

4. MVCC多版本并发控制

MySQL大多数事务型存储引擎实现了MVCC,可提升并发性。MVCC通过保存数据在某个时间点的快照实现,避免了加锁操作。InnoDB的MVCC通过在每行记录后保存两个隐藏列(创建时间和过期时间,实际为系统版本号)实现。在REPEATABLE READ隔离级别下,SELECT操作只查找版本早于当前事务版本且未被删除的数据行;INSERT操作保存当前系统版本号作为行版本号;DELETE操作保存当前系统版本号作为行删除标识;UPDATE操作插入新记录保存当前系统版本号作为行版本号,原记录保存当前系统版本号作为删除标识。MVCC只在COMMITTED READ和REPEATABLE READ两种隔离级别下工作。

5. 事务日志

InnoDB通过日志显著降低了提交事务时的开销。由于事务已被记录在日志中,就无需在每次事务提交时将缓冲池中的脏块刷新到磁盘。

事务对数据和索引的修改通常会映射到表空间的随机位置,因此将这些变更刷新到磁盘需要大量随机I/O操作。InnoDB考虑到在常规磁盘中,随机I/O比顺序I/O昂贵得多。这是因为一次随机I/O请求需要花费时间将磁头移动到正确位置,等待磁盘旋转到所需部分并读取,之后还要将磁头移回起始位置。

为解决这一问题,InnoDB利用日志将随机I/O转换为顺序I/O。一旦日志安全写入磁盘,事务就实现了持久化。即便发生断电,InnoDB也能通过重放日志恢复已提交的事务。此外,InnoDB使用一个后台线程智能地将变更刷新到数据文件。该线程能够批量组合写入操作,使数据写入更加顺序化,从而显著提高效率。

事务日志在提升事务效率方面作用显著:存储引擎在修改表数据时,只需修改内存拷贝,并将修改行为记录到持久化在硬盘上的事务日志中,而不必每次都将修改的数据本身持久化到磁盘。事务日志采用追加方式记录,因此写日志操作属于磁盘上一小块区域内的顺序I/O,相较于需要在磁盘多个位置移动磁头的随机I/O,速度要快得多。事务日志持久化后,内存中被修改的数据可在后台慢慢刷回到磁盘。若数据修改已记录到事务日志并持久化,但数据本身尚未写回磁盘时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。

当前,大多数存储引擎都采用预写式日志(Write - Ahead Logging),即修改数据时需要写两次磁盘。

6. 事务的实现

事务的实现依赖于数据库的存储引擎,不同存储引擎对事务的支持程度存在差异。在MySQL中,InnoDB和NDB是支持事务的存储引擎。事务的实现本质上是对ACID特性的实现。事务的隔离性借助锁来达成,而事务的原子性、一致性和持久性则通过事务日志实现。

7. 事务日志的构成

事务日志包含重做日志redo和回滚日志undo。

  1. redo log(重做日志)实现持久化和原子性
    在InnoDB存储引擎中,事务日志通过重做日志和InnoDB存储引擎的日志缓冲(InnoDB Log Buffer)协同工作。事务开启后,事务中的操作会先写入存储引擎的日志缓冲。在事务提交前,这些缓冲的日志必须提前刷新到磁盘上持久化,这就是常说的“日志先行”(Write - Ahead Logging)。事务提交后,Buffer Pool中映射的数据文件才会逐渐刷新到磁盘。若数据库崩溃或宕机,系统重启恢复时,可依据redo log中记录的日志,将数据库恢复到崩溃前的状态。对于未完成的事务,可根据恢复策略选择继续提交或回滚。
    系统启动时,会为redo log分配一块连续的存储空间,并以顺序追加的方式记录Redo Log,利用顺序IO提升性能。所有事务共享redo log的存储空间,它们的Redo Log按语句执行顺序依次交替记录。
  2. undo log(回滚日志)实现一致性
    undo log主要服务于事务的回滚。在事务执行过程中,除记录redo log外,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,若事务执行过程中需要回滚,可依据undo log进行回滚操作。单个事务的回滚仅影响当前事务的操作,不会干扰其他事务。Undo记录已部分完成且写入硬盘的未完成事务,默认情况下,回滚日志记录在表空间中(共享表空间或独享表空间)。

两种日志都可视为恢复操作。redo_log用于恢复提交事务对页的修改操作,undo_log则用于将行记录回滚到特定版本。二者记录内容不同,redo_log是物理日志,记录页的物理修改操作;undo_log是逻辑日志,按每行记录进行记录。

8.拓展:MySQL中的其他日志

  1. 错误日志:记录出错信息,同时也记录一些警告信息或正确信息。
  2. 查询日志:记录所有对数据库的请求信息,无论这些请求是否执行成功。
  3. 慢查询日志:设置一个阈值,将运行时间超过该阈值的所有SQL语句记录到慢查询日志文件中。
  4. 二进制日志:记录对数据库执行的所有更改操作。
  5. 中继日志:也是二进制日志,用于slave库的恢复。

9.分布式事务相关

MySQL对分布式事务的支持
分布式事务的实现方式多样,既可以利用InnoDB提供的原生事务支持,也可借助消息队列实现分布式事务的最终一致性。这里重点探讨InnoDB对分布式事务的支持。
MySQL自5.0.3版本的InnoDB存储引擎开始支持XA协议的分布式事务。一个分布式事务涉及多个事务性的行动,所有行动必须一起成功完成或一起回滚。
在MySQL中,使用分布式事务涉及一个或多个资源管理器和一个事务管理器。其分布式事务模型包含应用程序(AP)、资源管理器(RM)、事务管理器(TM)三个部分:

  1. 应用程序:定义事务边界,明确需要执行的事务内容。
  2. 资源管理器:提供访问事务的方法,通常一个数据库就是一个资源管理器。
  3. 事务管理器:协调参与全局事务的各个事务。

分布式事务采用两段式提交(two - phase commit)方式:

  1. 第一阶段:所有事务节点开始准备,向事务管理器表明ready状态。
  2. 第二阶段:事务管理器告知每个节点是commit还是rollback。若有一个节点失败,则全局节点全部rollback,以此保障事务的原子性。

七、MySQL锁机制

1.锁的基本概念

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统计算资源(如CPU、RAM、I/O等)的争用外,数据也是众多用户共享的资源。数据库锁定机制旨在保证数据的一致性,使各种共享资源在被并发访问时变得有序。例如,在淘宝购物场景中,当一件商品库存仅剩一件,同时有两人购买时,就需要借助事务和锁来解决谁能成功购买的问题。在该场景中,从库存表取出物品数量、插入订单、付款后插入付款表信息以及更新商品数量的过程中,锁用于保护有限资源,平衡隔离和并发的矛盾。

2.锁的分类

  1. 从对数据操作的类型分类
    • 读锁(共享锁):针对同一份数据,多个读操作可同时进行,互不影响。
    • 写锁(排他锁):当前写操作完成前,会阻断其他写锁和读锁。
  2. 从对数据操作的粒度分类
    为提高数据库并发度,每次锁定的数据范围越小越好。但管理锁需消耗资源(涉及获取、检查、释放锁等操作),因此数据库系统需在高并发响应和系统性能间平衡,由此产生“锁粒度(Lock granularity)”概念。
    • 表级锁:开销小,加锁快,不会出现死锁;但锁定粒度大,锁冲突概率高,并发度最低(MyISAM和MEMORY存储引擎采用表级锁)。
    • 行级锁:开销大,加锁慢,可能出现死锁;锁定粒度最小,锁冲突概率最低,并发度最高(InnoDB存储引擎既支持行级锁也支持表级锁,默认采用行级锁)。
    • 页面锁:开销和加锁时间介于表锁和行锁之间,可能出现死锁,锁定粒度介于表锁和行锁之间,并发度一般。
    • 适用场景:从锁的角度看,表级锁更适用于以查询为主,仅有少量按索引条件更新数据的应用,如Web应用;行级锁则更适用于有大量按索引条件并发更新少量不同数据,同时伴有并发查询的应用,如在线事务处理(OLTP)系统。

3.不同存储引擎的锁机制

  1. MyISAM表锁
    MyISAM的表锁有两种模式:
    • 表共享读锁(Table Read Lock):不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求。
    • 表独占写锁(Table Write Lock):会阻塞其他用户对同一表的读和写操作。
      MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作,其他线程的读、写操作都需等待锁被释放。默认情况下,写锁比读锁优先级高,锁释放时,优先满足写锁队列中的获取锁请求,再处理读锁队列。
  2. InnoDB行锁
    InnoDB实现了以下两种类型的行锁:
    • 共享锁(S):允许一个事务读取一行数据,阻止其他事务获取相同数据集的排他锁。
    • 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
      为实现行锁和表锁共存,InnoDB引入两种内部使用的意向锁(Intention Locks),且这两种意向锁都是表锁:
    • 意向共享锁(IS):事务打算给数据行加行共享锁时,需先取得该表的IS锁。
    • 意向排他锁(IX):事务打算给数据行加行排他锁时,需先取得该表的IX锁。
      索引失效可能导致行锁变为表锁,例如varchar类型查询不使用单引号的情况。

4.加锁机制

乐观锁与悲观锁是两种并发控制思想,可用于解决丢失更新问题。

  1. 乐观锁:乐观地假定大概率不会发生并发更新冲突,在访问、处理数据过程中不加锁,仅在更新数据时根据版本号或时间戳判断是否有冲突,有冲突则处理,无冲突则提交事务。乐观锁常用数据版本(Version)记录机制实现。
  2. 悲观锁:悲观地假定大概率会发生并发更新冲突,在访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁。悲观锁由数据库自身实现,使用时直接调用数据库相关语句即可。
  3. InnoDB的行锁算法
    • 记录锁(Record Locks):对单个行记录加锁,锁定符合条件的行。其他事务无法修改和删除加锁项。例如:SELECT * FROM table WHERE id = 1 FOR UPDATE;会在id = 1的记录上加记录锁,阻止其他事务插入、更新、删除该行数据。在通过主键索引与唯一索引对数据行进行UPDATE操作时,也会对该行数据加记录锁,如UPDATE SET age = 50 WHERE id = 1;(假设id列为主键列或唯一索引列)。
    • 间隙锁(Gap Locks):当使用范围条件而非相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁。对于键值在条件范围内但不存在的记录(即“间隙”),InnoDB也会加锁,这就是间隙锁机制。间隙锁锁定索引项之间的“间隙”(对第一条记录前的间隙或最后一条记录后的间隙加锁),不包含索引项本身。其他事务无法在锁范围内插入数据,防止了幻影行的产生。间隙锁基于非唯一索引,锁定一段范围内的索引记录。例如:SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;会锁住(1,10)区间内的记录行,id为2、3、4、5、6、7、8、9的数据行插入会被阻塞,但1和10两条记录行不会被锁住。GAP锁主要用于防止同一事务的两次当前读出现幻读情况。
    • 临键锁(Next - key Locks):临键锁是记录锁与间隙锁的组合,其封锁范围既包含索引记录,又包含索引区间。临键锁主要用于避免幻读(Phantom Read)。若将事务隔离级别降级为RC,临键锁会失效。Next - Key可理解为一种特殊的间隙锁或算法,通过临键锁可解决幻读问题。每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需注意,InnoDB中行级锁基于索引实现,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。对于行的查询,通常采用该方法解决幻读问题。

5.select for update的作用

for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中生效。在事务操作中,通过“for update”语句,MySQL会对查询结果集中每行数据添加排他锁,其他线程对该记录的更新与删除操作将被阻塞。排他锁包含行锁和表锁。
InnoDB的行锁实现特点决定了只有通过索引条件检索数据,InnoDB才使用行级锁,否则将使用表锁。例如在表单products中(假设id是主键):

明确指定主键且有对应数据时,使用row lock:

SELECT * FROM products WHERE id='3' FOR UPDATE;
SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE;

明确指定主键但查无此数据时,无lock:

 SELECT * FROM products WHERE id=' - 1' FOR UPDATE;

无主键时,使用table lock:

SELECT * FROM products WHERE name='Mouse' FOR UPDATE;

主键不明确时,使用table lock:

SELECT * FROM products WHERE id<>'3' FOR UPDATE;
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;

注1:FOR UPDATE仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效。
注2:可利用MySQL的Command Mode打开两个窗口进行锁定状况的测试。

6.死锁问题

  1. 死锁产生原因
    • 死锁指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而形成恶性循环。
    • 当事务试图以不同顺序锁定资源,或多个事务同时锁定同一个资源时,都可能产生死锁。锁的行为和顺序与存储引擎相关,同样顺序执行语句,不同存储引擎可能出现死锁情况不同,死锁原因包括真正的数据冲突以及存储引擎的实现方式。
  2. 死锁检测与恢复
    数据库系统实现了多种死锁检测和死锁超时机制。InnoDB存储引擎能检测到死锁的循环依赖并立即返回错误。死锁发生后,只有部分或完全回滚其中一个事务才能打破死锁。InnoDB目前处理死锁的方法是回滚持有最少行级排他锁的事务。事务型应用程序设计时必须考虑如何处理死锁,多数情况下重新执行因死锁回滚的事务即可。
    在涉及外部锁或表锁时,InnoDB可能无法完全自动检测到死锁,此时需通过设置锁等待超时参数innodb_lock_wait_timeout来解决。
    死锁会影响性能,但不会产生严重错误,因为InnoDB会自动检测死锁并回滚其中一个受影响的事务。在高并发系统中,当众多线程等待同一把锁时,死锁检测可能导致速度变慢。有时,禁用死锁检测(使用innodb_deadlock_detect配置选项),依赖innodb_lock_wait_timeout设置进行事务回滚可能更有效。
  3. MyISAM避免死锁
    在自动加锁情况下,MyISAM总是一次获得SQL语句所需的全部锁,因此MyISAM表不会出现死锁。
  4. InnoDB避免死锁
    • 为在单个InnoDB表上执行多个并发写入操作时避免死锁,可在事务开始时通过为预期要修改的每个元祖(行)使用SELECT… FOR UPDATE语句获取必要的锁,即便这些行的更改语句稍后执行。
    • 在事务中,若要更新记录,应直接申请足够级别的排他锁,而非先申请共享锁、更新时再申请排他锁,以免在申请排他锁时因其他事务已获得相同记录的共享锁而造成锁冲突甚至死锁。
    • 若事务需修改或锁定多个表,应在每个事务中以相同顺序使用加锁语句。在应用中,若不同程序会并发存取多个表,尽量约定以相同顺序访问表,可大幅降低死锁发生几率。
    • 通过SELECT… LOCK IN SHARE MODE获取行的读锁后,若当前事务需对该记录进行更新操作,很可能造成死锁。
    • 改变事务隔离级别也可能对死锁情况产生影响。
      若出现死锁,可使用show engine innodb status;命令确定最后一个死锁产生的原因。返回结果包含死锁相关事务的详细信息,如引发死锁的SQL语句、事务已获得的锁、正在等待的锁以及被回滚的事务等,据此可分析死锁原因并制定改进措施。

八、MySQL调优

日常工作中你是怎么优化SQL的?
SQL优化的一般步骤是什么,怎么看执行计划(explain),如何理解其中各个字段的含义?
如何写sql能够有效的使用到复合索引?
一条sql执行过长的时间,你如何优化,从哪些方面入手?
什么是最左前缀原则?什么是最左匹配原则?

1.影响mysql的性能因素

业务需求对MySQL的影响(合适合度)
存储定位对MySQL的影响
  • 不适合放进MySQL的数据
    • 二进制多媒体数据
    • 流水队列数据
    • 超大文本数据
  • 需要放进缓存的数据
    • 系统各种配置及规则数据
    • 活跃用户的基本信息数据
    • 活跃用户的个性化定制信息数据
    • 准实时的统计信息数据
    • 其他一些访问频繁但变更较少的数据
Schema设计对系统的性能影响
  • 尽量减少对数据库访问的请求
  • 尽量减少无用数据的查询请求
硬件环境对系统性能的影响

2.性能分析

MySQL Query Optimizer

MySQL 中有专门负责优化 SELECT 语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的 Query 提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是 DBA 认为是最优的,这部分最耗费时间)。

当客户端向 MySQL 请求一条 Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给 MySQL Query Optimizer 时,MySQL Query Optimizer 首先会对整条 Query 进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Query 的执行计划。如果没有 Hint 或 Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。

MySQL常见瓶颈
  • CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
  • IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
  • 服务器硬件的性能瓶颈:top,free,iostat 和 vmstat来查看系统的性能状态
性能下降SQL慢 执行时间长 等待时间长 原因分析
  • 查询语句写的烂
  • 索引失效(单值、复合)
  • 关联查询太多join(设计缺陷或不得已的需求)
  • 服务器调优及各个参数设置(缓冲、线程数等)
MySQL常见性能分析手段

在优化MySQL时,通常需要对数据库进行分析,常见的分析手段有慢查询日志,EXPLAIN 分析查询,profiling分析以及show命令查询系统状态及系统变量,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。

性能瓶颈定位

我们可以通过 show 命令查看 MySQL 状态及变量,找到系统的瓶颈:

Mysql> show status --显示状态信息(扩展show status like ‘XXX’)
Mysql> show variables --显示系统变量(扩展show variables like ‘XXX’)
Mysql> show innodb status --显示InnoDB存储引擎的状态
Mysql> show processlist --查看当前SQL执行,包括执行状态、是否锁表等
Shell> mysqladmin variables -u username -p password --显示系统变量
Shell> mysqladmin extended-status -u username -p password --显示状态信息
Explain(执行计划)
  • 是什么:使用 Explain 关键字可以模拟优化器执行SQL查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈
  • 能干吗
    • 表的读取顺序
    • 数据读取操作的操作类型
    • 哪些索引可以使用
    • 哪些索引被实际使用
    • 表之间的引用
    • 每张表有多少行被优化器查询
  • 怎么玩
    • Explain + SQL语句
    • 执行计划包含的信息(如果有分区表的话还会有partitions)
各字段解释
  • id(select 查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序)

    • id相同,执行顺序从上往下
    • id全不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
    • id部分相同,执行顺序是先按照数字大的先执行,然后数字相同的按照从上往下的顺序执行
  • select_type(查询类型,用于区别普通查询、联合查询、子查询等复杂查询)

    • SIMPLE :简单的select查询,查询中不包含子查询或UNION
    • PRIMARY:查询中若包含任何复杂的子部分,最外层查询被标记为PRIMARY
    • SUBQUERY:在select或where列表中包含了子查询
    • DERIVED:在from列表中包含的子查询被标记为DERIVED,MySQL会递归执行这些子查询,把结果放在临时表里
    • UNION:若第二个select出现在UNION之后,则被标记为UNION,若UNION包含在from子句的子查询中,外层select将被标记为DERIVED
    • UNION RESULT:从UNION表获取结果的select
  • table(显示这一行的数据是关于哪张表的)

  • type(显示查询使用了那种类型,从最好到最差依次排列 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL )

    • system:表只有一行记录(等于系统表),是 const 类型的特例,平时不会出现
    • const:表示通过索引一次就找到了,const 用于比较 primary key 或 unique 索引,因为只要匹配一行数据,所以很快,如将主键置于 where 列表中,mysql 就能将该查询转换为一个常量
    • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
    • ref:非唯一性索引扫描,范围匹配某个单独值得所有行。本质上也是一种索引访问,他返回所有匹配某个单独值的行,然而,它可能也会找到多个符合条件的行,多以他应该属于查找和扫描的混合体
    • range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需开始于索引的某一点,而结束于另一点,不用扫描全部索引
    • index:Full Index Scan,index于ALL区别为index类型只遍历索引树。通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
    • ALL:Full Table Scan,将遍历全表找到匹配的行

    tip: 一般来说,得保证查询至少达到range级别,最好到达ref

  • possible_keys(显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用)

  • key:实际使用的索引,如果为NULL,则没有使用索引。查询中若使用了覆盖索引,则该索引和查询的 select 字段重叠,仅出现在key列表中

  • key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的

  • ref(显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值)

  • rows(根据表统计信息及索引选用情况,大致估算找到所需的记录所需要读取的行数)

  • Extra(包含不适合在其他列中显示但十分重要的额外信息)

    • using filesort: 说明mysql会对数据使用一个外部的索引排序,不是按照表内的索引顺序进行读取。mysql中无法利用索引完成的排序操作称为“文件排序”。常见于order by和group by语句中
    • Using temporary:使用了临时表保存中间结果,mysql在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。
    • using index:表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率不错,如果同时出现using where,表明索引被用来执行索引键值的查找;否则索引被用来读取数据而非执行查找操作
    • using where:使用了where过滤
    • using join buffer:使用了连接缓存
    • impossible where:where子句的值总是false,不能用来获取任何元祖
    • select tables optimized away:在没有group by子句的情况下,基于索引优化操作或对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化
    • distinct:优化distinct操作,在找到第一匹配的元祖后即停止找同样值的动作

case:

  • 第一行(执行顺序4):id列为1,表示是union里的第一个select,select_type列的primary表示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1.name…】
  • 第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【select id,name from t1 where other_column=‘’】
  • 第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select。【select id from t3】
  • 第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行【select name,id from t2】
  • 第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的表示用第一个和第四个select的结果进行union操作。【两个结果union操作】
慢查询日志

MySQL 的慢查询日志是 MySQL 提供的一种日志记录,它用来记录在 MySQL 中响应时间超过阈值的语句,具体指运行时间超过 long_query_time 值的 SQL,则会被记录到慢查询日志中。

long_query_time 的默认值为10,意思是运行10秒以上的语句。默认情况下,MySQL数据库没有开启慢查询日志,需要手动设置参数开启。

  • 查看开启状态
SHOW VARIABLES LIKE '%slow_query_log%'
  • 开启慢查询日志
    • 临时配置
mysql> set global slow_query_log='ON';
mysql> set global slow_query_log_file='/var/lib/mysql/hostname-slow.log';
mysql> set global long_query_time=2;

也可set文件位置,系统会默认给一个缺省文件host_name - slow.log。使用set操作开启慢查询日志只对当前数据库生效,如果MySQL重启则会失效。
- 永久配置:修改配置文件my.cnf或my.ini,在[mysqld]一行下面加入两个配置参数

[mysqld]
slow_query_log = ON
slow_query_log_file = /var/lib/mysql/hostname-slow.log
long_query_time = 3

注:log - slow - queries 参数为慢查询日志存放的位置,一般这个目录要有 MySQL 的运行帐号的可写权限,一般都将这个目录设置为 MySQL 的数据存放目录;long_query_time = 2 中的 2 表示查询超过两秒才记录;在my.cnf或者 my.ini 中添加 log - queries - not - using - indexes 参数,表示记录下没有使用索引的查询。

可以用 select sleep(4) 验证是否成功开启。在生产环境中,如果手工分析日志,查找、分析SQL,还是比较费劲的,所以MySQL提供了日志分析工具mysqldumpslow。

# 得到返回记录集最多的10个SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log
# 得到访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/hostname-slow.log
# 得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/hostname-slow.log

也可以和管道配合使用

mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log | more

也可使用 pt - query - digest 分析 RDS MySQL 慢查询日志

Show Profile 分析查询

通过慢日志查询可以知道哪些 SQL 语句执行效率低下,通过 explain 我们可以得知 SQL 语句的具体执行情况,索引使用等,还可以结合Show Profile命令查看执行状态。

Show Profile 是 MySQL 提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量。

默认情况下,参数处于关闭状态,并保存最近15次的运行结果。

  • 分析步骤
    • 是否支持,看看当前的mysql版本是否支持
mysql>Show  variables like 'profiling';  --默认是关闭,使用前需要开启
- **开启功能**,默认是关闭,使用前需要开启
mysql>set profiling=1;  
- **运行SQL**
- **查看结果**
mysql> show profiles;
+----------+------------+---------------------------------+
| Query_ID | Duration   | Query                           |
+----------+------------+---------------------------------+
|        1 | 0.00385450 | show variables like "profiling" |
|        2 | 0.00170050 | show variables like "profiling" |
|        3 | 0.00038025 | select * from t_base_user       |
+----------+------------+---------------------------------+
- **诊断SQL**,show profile cpu,block io for query  id(上一步前面的问题SQL数字号码)
日常开发需要注意的结论
  • converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。
  • create tmp table 创建临时表,这个要注意
  • Copying to tmp table on disk 把内存临时表复制到磁盘
  • locked
查询中哪些情况不会使用索引?

3.性能优化

索引优化
  • 全值匹配我最爱
  • 最佳左前缀法则,比如建立了一个联合索引(a,b,c),那么其实我们可利用的索引就有(a), (a,b), (a,b,c)
  • 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
  • 存储引擎不能使用索引中范围条件右边的列
  • 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select
  • is null ,is not null 也无法使用索引
  • like “xxxx%” 是可以用到索引的,like “%xxxx” 则不行(like “%xxx%” 同理)。like以通配符开头(‘%abc…’)索引失效会变成全表扫描的操作
  • 字符串不加单引号索引失效
  • 少用or,用它来连接时会索引失效
  • <,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 则不行,会导致全表扫描
一般性建议
  • 对于单键索引,尽量选择针对当前query过滤性更好的索引
  • 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
  • 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
  • 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的
  • 少用Hint强制索引
查询优化
  • 永远小标驱动大表(小的数据集驱动大的数据集)
slect * from A where id in (select id from B)
#等价于
select id from B
select * from A where A.id=B.id

当 B 表的数据集必须小于 A 表的数据集时,用 in 优于 exists

select * from A where exists (select 1 from B where B.id=A.id)
#等价于
select * from A
select * from B where B.id = A.id

当 A 表的数据集小于B表的数据集时,用 exists优于用 in。注意:A表与B表的ID字段应建立索引。

GROUP BY 关键字优化

GROUP BY 操作本质上是先排序后分组,因此要遵循索引的最左前缀原则。当无法使用索引列时,可以通过增大 max_length_for_sort_datasort_buffer_size 参数的设置来优化性能。同时,由于 WHERE 子句在 HAVING 子句之前执行,能在 WHERE 子句中限定的条件就不要使用 HAVING 子句。

数据类型优化

MySQL 支持多种数据类型,选择合适的数据类型对于提高性能至关重要。以下是一些选择数据类型的基本原则:

  • 更小通常更好:一般情况下,应尽量使用能够正确存储数据的最小数据类型,以减少存储空间和提高查询效率。
  • 简单就好:简单的数据类型通常需要更少的 CPU 周期。例如,整数的操作代价低于字符,因为字符集和校对规则会使字符比较比整型比较更复杂。
  • 尽量避免 NULL:通常最好将列指定为 NOT NULL,因为 NULL 值会增加额外的存储和处理开销。

九、分区、分表、分库

1.MySQL 分区

通常,我们创建的表对应一组存储文件。使用 MyISAM 存储引擎时,是一个 .MYI.MYD 文件;使用 InnoDB 存储引擎时,是一个 .ibd.frm(表结构)文件。当数据量较大(一般达到千万条记录级别以上)时,MySQL 的性能会下降。此时,需要将数据分散到多组存储文件中,以保证单个文件的执行效率。

分区的作用

  • 实现逻辑数据分割。
  • 提高单一的写和读应用速度。
  • 提高分区范围读查询的速度。
  • 分割数据可以使用多个不同的物理文件路径。
  • 高效保存历史数据。

查看数据库是否支持分区

  • MySQL 5.6 以及之前版本:使用 SHOW VARIABLES LIKE '%partition%'; 查看。
  • MySQL 5.6:使用 show plugins; 查看。

分区类型及操作

  • RANGE 分区:基于给定连续区间的列值,将多行数据分配到不同分区。MySQL 会根据指定的拆分策略,将数据存放在不同的表文件上。从外部看,仍然是一张表,对用户透明。通常按照时间范围进行分区,如交易表、销售表等可以按年月存放数据。但可能会产生热点问题,大量流量集中在最新的数据上。不过,这种分区方式在扩容时较为简单。
  • LIST 分区:类似于 RANGE 分区,但每个分区必须明确定义。其主要区别在于,LIST 分区中每个分区的定义和选择基于某列的值属于一个值列表集,而 RANGE 分区基于连续区间值的集合。
  • HASH 分区:根据用户定义的表达式返回值进行分区,该表达式使用插入表的行的列值计算。此函数可以是 MySQL 中有效的、产生非负整数值的任何表达式。HASH 分区的好处是可以平均分配每个库的数据量和请求压力,但扩容比较麻烦,需要进行数据迁移,重新计算 hash 值并分配到不同的库或表。
  • KEY 分区:类似于 HASH 分区,区别在于 KEY 分区只支持计算一列或多列,且由 MySQL 服务器提供自身的哈希函数,必须有一列或多列包含整数值。

虽然分区表有一定优势,但大部分互联网企业更倾向于自己分库分表进行水平扩展,原因如下:

  • 分区表的分区键设计不够灵活,如果查询不使用分区键,容易出现全表锁。
  • 当数据并发量增加时,在分区表上实施关联操作会带来灾难。
  • 自己分库分表可以更好地掌控业务场景和访问模式,而分区表的执行方式对于研发人员来说不太可控。

随着业务发展,数据量增大,高并发读写操作超出单个数据库服务器的处理能力时,就需要进行数据分片。数据分片是指按照某个维度将单一数据库中的数据分散存放到多个数据库或表中,分库和分表是实现数据分片的有效手段。与分区不同,分区一般在单机中实现,常用时间范围分区方便归档,而分库分表需要代码实现,二者并不冲突,可以结合使用。

2.分库与分表的设计

MySQL 分表
分表有垂直拆分和水平拆分两种方式。

  • 垂直拆分:通常根据业务功能的使用频次,将主要的、热门的字段放在一起作为主要表,将不常用的字段按照业务属性聚集,拆分到不同的次要表中。主要表和次要表的关系一般为一对一。
  • 水平拆分(数据分片):当单表容量超过 500W 时,建议进行水平拆分。水平拆分是将一个表复制成具有相同表结构的不同表,然后按照一定规则划分数据,分别存储到这些表中,以保证单表容量不会过大,提升性能。这些结构相同的表可以放在一个或多个数据库中。常见的水平分割方法有:
    • 使用 MD5 哈希:对 UID 进行 md5 加密,取前几位(如前两位),将不同的 UID 哈希到不同的用户表(如 user_xx)中。
    • 按时间划分:根据时间将数据放入不同的表,如 article_201601article_201602
    • 按热度拆分:将高点击率的词条生成各自的表,低热度的词条放在一张大表中。当低热度词条达到一定数量后,再将其单独拆分成一张表。
    • 根据 ID 值划分:将用户数据按 ID 范围放入对应的表,如第一个表为 user_0000,第二个 100 万的用户数据放在 user_0001 中,随着用户增加,直接添加用户表即可。
      MySQL三万字精华面试汇总(收藏系列)_第10张图片

MySQL 分库

  • 分库的原因:在数据库集群环境中,多台 slave 基本可以满足读取操作,但写入或大数据、频繁的写入操作会对 master 性能产生较大影响。单库无法解决大规模并发写入的问题,因此需要考虑分库。
  • 分库的定义:当一个库中的表过多,导致海量数据,系统性能下降时,将原本存储在一个库的表拆分存储到多个库上。通常按照功能模块、关系密切程度划分表,部署到不同的库中。
  • 分库的优点
    • 减少增量数据写入时的锁对查询的影响。
    • 由于单表数量减少,常见的查询操作需要扫描的记录减少,单表单次查询所需的检索行数变少,减少了磁盘 IO,降低了时延。
  • 分库的局限性:无法解决单表数据量过大的问题。

分库分表后的难题

  • 分布式事务问题,需要保证数据的完整性和一致性。
  • 数据操作维度问题,如用户、交易、订单等不同维度的查询和分析,以及跨库联合查询问题,可能需要进行两次查询。
  • 跨节点的 countorder bygroup by 以及聚合函数问题,可能需要在各个节点上得到结果后在应用程序端进行合并。
  • 额外的数据管理负担,如访问数据表的导航定位。
  • 额外的数据运算压力,需要在多个节点执行操作,然后再合并计算。
  • 程序编码开发难度提升,目前没有很好的框架解决,更多依赖业务来确定如何分库分表和合并数据。

十、主从复制

1.复制的基本原理

slave 会从 master 读取 binlog 来进行数据同步,主要包括以下三个步骤:

  1. master 将数据变更记录到二进制日志(binary log),这些记录过程称为二进制日志事件(binary log events)。
  2. slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)。
  3. slave 重做中继日志中的事件,将变更应用到自己的数据库中。MySQL 复制是异步且串行化的。
2.复制的基本原则
  • 每个 slave 只有一个 master。
  • 每个 slave 只能有一个唯一的服务器 ID。
  • 每个 master 可以有多个 slave。
3.复制的最大问题

主从复制存在延时问题。

十一、其他问题

1.说一说三个范式
  • 第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这些单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。
  • 第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指组合关键字中的某些字段决定非关键字段的情况),即所有非关键字段都完全依赖于任意一组候选关键字。
  • 第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖,则符合第三范式。传递函数依赖指如果存在 “A → B → C” 的决定关系,则 C 传递函数依赖于 A。因此,满足第三范式的数据库表不应存在 “关键字段 → 非关键字段 x → 非关键字段 y” 的依赖关系。
2.百万级别或以上的数据如何删除

由于索引需要额外的维护成本,索引文件单独存在,对数据进行增加、修改、删除操作时,会产生额外的对索引文件的操作,消耗额外的IO,降低增、改、删的执行效率。根据 MySQL 官方手册,删除数据的速度与创建的索引数量成正比。

因此,删除百万级数据时,可以按以下步骤操作:

  1. 先删除索引,此过程大概耗时三分多钟。
  2. 删除无用数据,该过程不到两分钟。
  3. 数据删除完成后重新创建索引。此时数据量较少,创建索引速度较快,约十分钟左右。

这种方法比直接删除数据要快很多,而且避免了删除中断导致的回滚问题。

你可能感兴趣的:(mysql,面试,数据库)