文章目录
- 数据库
- 一、基本概念
- 二、MySQL数据库
- 2.1 MySQL基础
- 1、MySQL数据库的优点?
- 2、MySQL支持的数据类型有?
- Q:varchar 和 char 的区别?
- Q:blob 和 text 的区别?
- Q:datetime 和 timestamp 的区别?
- Q:MySQL中记录货币使用什么字段类型?
- Q:MySQL中怎么存储表情emoji?
- 3、MySQL中的约束有?
- 4、什么是内连接、外连接、笛卡尔积?
- 5、连接查询时,大表和小表应该怎么选择?
- Q:怎么区分那个是驱动表与被驱动表?
- Q:JOIN查询如何选择驱动表与被驱动表?
- 6、SQL语句的执行顺序?
- 7、MySQL的基础架构?
- 8、SQL查询语句在MySQL中如何运行?
- 2.2 操作指令
- Q:where和having区别
- Q:in 和 exists 的区别?
- Q:drop、delete 和 truncate 的区别?
- Q:union 和 union all 的区别?
- Q:count(1)、count(*) 和 count(列名) 的区别?
- 2.3 存储引擎
- 1、MySQL中常见的存储引擎?
- 2、MySQL存储引擎的结构?
- 3、MyISAM 和 InnoDB 的区别?
- 4、MySQL查询缓存
- 2.4 事务
- 1、什么是事务?
- Q:什么是数据库事务?
- 2、事务的四大特性ACID
- 3、事务的ACID如何保证?
- 4、事务的隔离级别有哪些?
- 5、什么是脏读、幻读、不可重复读?
- Q:不可重复读和幻读的区别?
- 6、事务的隔离级别如何实现?
- 7、MVCC机制怎么实现的?
- 2.5 锁
- 1、MySQL中有哪些锁?
- 2、表锁和行锁的区别?
- Q:行锁使用需要注意什么?
- 3、共享锁和排他锁?
- 4、InnoDB中行锁如何实现?
- 5、当前读和快照读的区别?
- 6、意向锁是什么?
- 5、MySQL的乐观锁和悲观锁了解么?
- 6、MySQL死锁问题?
- 2.6 性能优化
- 1、读写分离机制?
- 2、主从复制原理?
- 3、主从同步延迟怎么解决?
- 4、分库
- 5、分表
- 6、水平分表的路由方式有哪些?
- 7、不停机扩容怎么实现?
- 8、常见的分库分表中间件有哪些?
- 9、分库分表会带来什么问题?
- 10、百万级别以上的数据如何删除?
- 11、百万千万级大表如何添加字段?
- 12、MySQL数据库CPU飙升,怎么处理?
- 13、能否使用MySQL存储文件(如图片等)?
- 14、MySQL如何存储IP地址?
- 14、慢SQL如何定位?
- 15、如何优化慢SQL语句?
- 16、怎么看执行计划(explain),其中字段的含义?
- 2.7 索引
- 1、什么是索引?有何作用?
- 2、索引的优缺点?
- 3、索引的分类有哪些?
- 4、创建索引需要注意的点?
- 5、索引什么时候会失效?
- 6、什么情况下不适合用索引?
- 7、MySQL中索引为什么选用B+树?
- 8、Hash索引和B+树索引的区别?
- 9、聚集索引和非聚集索引的区别?
- Q:聚集索引的选取规则?
- 10、回表了解么?
- 11、覆盖索引了解么?
- 12、什么是最左前缀原则?
- 13、什么是索引下推优化?
- 14、索引底层数据结构
- Q:为什么MySQL不使用Hash表作为索引的数据结构呢?
- Q:B树和B+树两者有何异同呢?
- 15、索引类型
- Q:非聚集索引一定会回表查询么?
- 16、MySQL中查询优化?
- 2.8 日志
- 1、MySQL日志文件有哪些?作用是什么?
- 2、redo log 日志
- 3、binlog日志
- 4、两阶段提交
- 5、undo log 日志
- 6、binlog 和 redo log的区别?
- 7、一条更新语句怎么执行?
- 8、为什么需要两阶段提交?
- 9、redo log日志怎么刷入磁盘?
- Q:什么时候会刷盘?
数据库
关系型数据库和非关系型数据库
关系型数据库: MySql、Oracle、DB2、SQLServer
非关系型数据库: Redis、Mongo db、MemCached
关系型数据库是指利用关系模型来组织数据的数据库,关系模型即二维表格模型。
非关系型数据库一般指非关系型、分布式的,且一般不保证ACID的数据库。
一、基本概念
- 数据库(DataBase,DB): 存储数据的仓库,数据是有组织的进行存储。
- 数据库管理系统(DataBase Management System,DBMS): 管理数据库的大型软件。
- SQL(Structured Query Lanagement): 结构化查询语言,操作关系型数据库的编程语言,定义了所有关系型数据库的统一标准。
- 数据库三大范式
**第一范式:**数据表中的每一列(每个字段)都不可以再拆分。例如用户表,用户地址还可以拆分成国家、省份、市,这样才是符合第一范式的。
**第二范式:**在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。例如订单表里,存储了商品信息(商品价格、商品类型),那就需要把商品 ID 和订单 ID 作为联合主键,才满足第二范式。
**第三范式:**在满足第二范式的基础上,表中的非主键只依赖于主键,而不依赖于其他非主键。例如订单表,就不能存储用户信息(姓名、地址)。
二、MySQL数据库
2.1 MySQL基础
1、MySQL数据库的优点?
- 速度:体积小、运行速度快、实用性强。
- 价格成本低:MySQL对多数个人来说是免费的。
- 支持大型数据库,可以处理上千万条记录。
- 容易使用,与其他大型数据库的设置和管理相比,其复杂程度较低,容易学习。
- MySQL支持多线程,可充分利用 CPU 资源。
- MySQL 使用标准的 SQL数据语言形式。
- 支持多种存储引擎。
- 可移植性:能够工作在众多不同的系统平台上,例如:Windows、Linux、Unix、MacOS等。
- 丰富的接口API:提供了用于C、C++、Eiffel、Java、Perl、PHP、Python、Rudy和TCL 等语言的API。
- 支持查询语言:MySQL可以利用标准SQL语法和支持JDBC(开放式数据库连接)的应用程序。
- 安全性和连接性:十分灵活和安全的权限和密码系统,允许主机验证。连接到服务器时,所有的密码均采用加密形式,从而保证了密码安全。并且由于MySQL是网络化的,因此可以在因特网上的任何地方访问,提高数据共享效率。
2、MySQL支持的数据类型有?
数值型、日期型、字符串型。
Q:varchar 和 char 的区别?
- char
① char 表示定长字符串,长度是固定的;② 如果插入数据的长度小于 char 的固定长度时,则用空格填充;③ 因为长度固定,所以存取速度要比 varchar 快很多,甚至能快 50%,但正因为其长度固定,所以会占据多余的空间,是空间换时间的做法;④ 对于 char 来说,最多能存放的字符个数为 255,和编码无关。 - varchar
① varchar 表示可变长字符串,长度是可变的;② 插入的数据是多长,就按照多长来存储;③ varchar 在存取方面与 char 相反,它存取慢,因为长度不固定,但正因如此,不占据多余的空间,是时间换空间的做法;④ 对于 varchar 来说,最多能存放的字符个数为 65532 。
Q:blob 和 text 的区别?
- blob 用于存储二进制数据,而 text 用于存储大字符串。
- blob 没有字符集,text 有一个字符集,并且根据字符集的校对规则对值进行排序和比较。
Q:datetime 和 timestamp 的区别?
- 相同点
① 两个数据类型存储时间的表现格式一致。均为 YYYY-MM-DD HH:MM:SS 。
② 两个数据类型都包含「日期」和「时间」部分。
③ 两个数据类型都可以存储微秒的小数秒(秒后 6 位小数秒)。 - 区别
① 日期范围:DATETIME的日期范围是1000-01-01 00:00:00.000000 到 9999-12-31 23:59:59.999999;TIMESTAMP 的时间范围是1970-01-01 00:00:01.000000 UTC到2038-01-09 03:14:07.999999 UTC 。
② 存储空间:DATETIME 的存储空间为 8 字节;TIMESTAMP 的存储空间为 4 字节。
③ 时区相关:DATETIME 存储时间与时区无关;TIMESTAMP 存储时间与时区有关,显示的值也依赖于时区。
④ 默认值:DATETIME 的默认值为 null ;TIMESTAMP 的字段默认不为空(not null),默认值为当前时间(CURRENT_TIMESTAMP)。
Q:MySQL中记录货币使用什么字段类型?
货币在数据库中 MySQL 常用 Decimal 和 Numric 类型表示,这两种类型被 MySQL 实现为同样的类型。他们被用于保存与货币有关的数据。
例如 salary DECIMAL(9,2),9(precision)代表将被用于存储值的总的小数位数,而 2(scale)代表将被用于存储小数点后的位数。存储在 salary 列中的值的范围是从-9999999.99 到 9999999.99。
DECIMAL 和 NUMERIC 值作为字符串存储,而不是作为二进制浮点数,以便保存那些值的小数精度。(字符串存储数据的好处,避免精度丢失)
之所以不使用float或者double的原因:因为float和double是以二进制存储的,所以有一定的误差。
Q:MySQL中怎么存储表情emoji?
MySQL 可以直接使用 字符串 存储 emoji。
但是需要注意的,utf8 编码是不行的,MySQL 中的 utf8 编码最多只用 3 个字节存储字符,所以存储不了表情。那该怎么办?需要使用 utf8mb4 编码。
3、MySQL中的约束有?
非空约束、唯一约束、主键约束、默认约束、检查约束、外键约束
注意:MySQL中不支持检查约束。
非空约束: 保证列中所有数据不能有null值(not null)。
唯一约束: 保证列中所有数据各不相同(unique)。
主键约束: 主键是一行数据的唯一标识,要求非空且唯一(primary key),一张表只能有一个主键。
检查约束: 保证列中的值满足某—条件(check)。
默认约束: 保存数据时,未指定值则采用默认值(default)。
外键约束: 外键用来让两个表的数据之间建立连接,保证数据的一致性和完整性(foreign key)。
4、什么是内连接、外连接、笛卡尔积?
笛卡尔积:取A,B集合所有组合情况。
多表查询:从多张表查询数据
√ 连接查询
-
内连接: 相当于查询AB交集数据。
-
外连接:
- 左外连接: 相当于查询A表所有数据和交集部分数据。
- 右外连接: 相当于查询B表所有数据和交集部分数据。
√ 子查询
-
内连接(查询AB交集)
① 隐式内连接select 字段列表 from 表1,表2 ...... where 条件;
② 显式内连接select 字段列表 from 表1 [inner] join 表2 on 条件;
-
外连接
① 左外连接(查询A表所有数据和交集部分数据)select 字段列表 from 表1 left [outer] join 表2 on 条件;
② 右外连接(查询B表所有数据和交集部分数据)select 字段列表 from 表1 right [outer] join 表2 on 条件;
-
子查询(查询中嵌套查询)
子查询根据查询结果不同,作用不同:
1、单行单列:作为条件值,使用 = != > < 等进行条件判断select 字段列表 from 表 where 字段名 = (子查询);
2、多行单列:作为条件值,使用in等关键字进行条件判断select 字段列表 from 表 where 字段名 in (子查询);
3、多行多列:作为虚拟表select 字段列表 from (子查询) where 条件;
5、连接查询时,大表和小表应该怎么选择?
在数据库查询中,经常用到表关联,听到最多的规则是 “小表驱动大表“ 。那么问题来了,什么是小表驱动大表?为什么要用小表驱动大表?怎么区分那个是驱动表与被驱动表?JOIN查询如何选择驱动表与被驱动表?索引应该建在驱动表还是被驱动表?
1. 什么是小表驱动大表?
小表驱动大表指的是用少量的数据集驱动大量的数据集。
2. 为什么要用小表驱动大表?
例如:现有两个表A与B,表A有200条数据,表B有20万条数据;按照循环的概念举个例子:
小表驱动大表 --> A驱动表,B被驱动表:for(200 条){for(20 万条){…}}
大表驱动小表 --> B驱动表,A被驱动表:for(20 万){for(200 条){…}}
总结:
如果小的循环在外层,对于表连接来说就只连接200次 ;
如果大的循环在外层,则需要进行20万次表连接,从而浪费资源,增加消耗;
综上:
小表驱动大表的主要目的是 通过减少表连接创建的次数,加快查询速度。
Q:怎么区分那个是驱动表与被驱动表?
通过EXPLAIN查看SQL语句的执行计划可以判断在谁是驱动表,EXPLAIN语句分析出来的第一行的表即是驱动表;
Q:JOIN查询如何选择驱动表与被驱动表?
在JOIN查询中经常用到的inner join、left join、right join。
问题解答:
- 当使用left join时,左表是驱动表,右表是被驱动表;
- 当使用right join时,右表时驱动表,左表是驱动表;
- 当使用inner join时,mysql会选择数据量比较小的表作为驱动表,大表作为被驱动表;
6、SQL语句的执行顺序?
7、MySQL的基础架构?
MySQL 逻辑架构图主要分3层:
- 客户端: 最上层的服务并不是 MySQL 所独有的,大多数基于网络的客户端/服务器的工具或者服务都有类似的架构。比如连接处理、授权认证、安全等等。
- Server层: 大多数 MySQL 的核心服务功能都在这一层,包括查询解析、分析、优化、缓存以及所有的内置函数(例如,日期、时间、数学和加密函数),所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
- 存储引擎层: 第三层包含了存储引擎。存储引擎负责 MySQL 中数据的存储和提取。Server 层通过 API 与存储引擎进行通信。这些接口屏蔽了不同存储引擎之间的差异,使得这些差异对上层的查询过程透明。
8、SQL查询语句在MySQL中如何运行?
- 先检查该语句 是否有权限,如果没有权限,直接返回错误信息,如果有权限会先查询缓存(MySQL8.0 版本以前)。
- 如果没有缓存,分析器进行 语法分析,提取 sql 语句中 select 等关键元素,然后判断 sql 语句是否有语法错误,比如关键词是否正确等等。
- 语法解析之后,MySQL 的服务器会对查询的语句进行优化,确定执行的方案。
- 完成查询优化后,按照生成的执行计划调用数据库引擎接口,返回执行结果。
2.2 操作指令
注意:
①条件查询的条件列表
Q:where和having区别
- 执行时机不一样: where是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤。
- 可判断的条件不一样: where不能对聚合函数进行判断,having可以。
执行顺序: where > 聚合函数 > having
Q:in 和 exists 的区别?
MySQL中的 in语句 是把外表和内表作 hash 连接,而 exists语句 是对外表作 loop 循环,每次loop循环再对内表进行查询。我们可能认为 exists 比 in 语句的效率要高,这种说法其实是不准确的,要区分情景:
- 如果查询的两个表大小相当,那么用 in 和 exists 差别不大。
- 如果两个表中一个较小,一个是大表,则子查询表大的用 exists,子查询表小的用 in。
- not in 和not exists:如果查询语句使用了not in,那么内外表都进行全表扫描,没有用到索引;而not exists的子查询依然能用到表上的索引。所以 无论哪个表大,用 not exists ↑ 都比 not in 要快 。
Q:drop、delete 和 truncate 的区别?
三者都表示删除,但存在一些区别,如下:
因此,在不再需要一张表的时候,用 drop(丢弃数据);在想删除部分数据行时候,用 delete(删除数据);在保留表而删除所有数据的时候用 truncate(清空数据)。三者执行速度:drop > truncate > delete 。
Q:union 和 union all 的区别?
① union all 将查询到的结果 直接合并在一起;union 会对查询到的结果进行去重操作。
② 效率 union ↑ 高于 union all。
Q:count(1)、count(*) 和 count(列名) 的区别?
- 执行效果
① count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为 null 的列。
② count(1)包括了忽略所有列,用 1 代表代码行,在统计结果的时候,不会忽略列值为 null 的列。
③ count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空的列(这里的空不是指空字符串或者 0,而是表示 null)的计数,即某个字段值为 null 时,不统计。 - 执行速度
① 列名为主键,count(列名) 会比 count(1) 快。
② 列名不为主键,count(1) 会比 count(列名) 快。
③ 如果表多个列并且没有主键,则 count(1) 的执行效率优于 count() 。
④ 如果有主键,则 select count(主键) 的执行效率是最优的。
⑤ 如果表只有一个字段,则 select count() 最优。
2.3 存储引擎
1、MySQL中常见的存储引擎?
MySQL支持多种存储引擎,你可以通过 show engines
命令来查看MySQL支持的所有存储引擎。常见的如:InnoDB、MyISAM、Memory。
还可以通过 select version()
命令查看MySQL的版本。
你也可以通过 show variables like "%storage_engine%"
命令直接 查看MySQL当前默认的存储引擎 。
如果你只想 查看数据库中某个表使用的存储引擎 的话,可以使用 show table status from db_name where name= 'table_name'
命令。
2、MySQL存储引擎的结构?
MySQL存储引擎采用的是 插件式架构,支持多种存储引擎,我们甚至可以为不同的数据库表设置不同的存储引擎以适应不同场景的需要。存储引擎是基于表的,而不是数据库(即不同的表可以使用不同的存储引擎)。
3、MyISAM 和 InnoDB 的区别?
1. 是否支持行级锁
MyISAM只有表级锁(table-level locking)。InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁。
2. 是否支持事务
MylSAM不提供事务支持。InnoDB提供事务支持,实现了SQL标准定义了四个隔离级别,具有提交(commit)和回滚(rollack)事务的能力。并且,InnoDB默认使用的 REPEATABLE-READ(可重读)隔离级别是可以解决幻读问题发生的(基于MVCC和Next-Key Lock)。
3. 是否支持外键
MylSAM不支持,而InnoDB支持。
总结:一般我们也是不建议在数据库层面使用外键的,应用层面可以解决。不过,这样会对数据的一致性造成威胁。具体要不要使用外键还是要根据你的项目来决定。
4. 是否支持数据库异常崩溃后的安全恢复
MylSAM不支持,而InnoDB支持(依赖于回滚日志)。
使用InnoDB的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于redo log 。
5. 是否支持MVCC(多版本并发控制)
MylSAM不支持,而InnoDB支持。
lnnoDB仅在叶子节点存储数据,非叶子结点仅仅作为遍历结点使用。
6. 索引实现不—样
虽然MyISAM引擎和InnoDB引擎都是使用B+树(仅在叶子节点存储数据,非叶子结点仅仅作为遍历结点使用)作为索引结构,但是两者的实现方式不太一样。
InnoDB引擎中,其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+树组织的一个索引结构,树的叶节点data域保存了完整的数据记录。
4、MySQL查询缓存
2.4 事务
1、什么是事务?
事务就是逻辑上的 一组操作,要么都执行,要么都不执行。
Q:什么是数据库事务?
大多数情况下,我们在谈论事务的时候,如果没有特指 分布式事务 ,往往指的就是 数据库事务 。
数据库事务在我们日常开发中接触的最多了。如果你的项目属于单体架构的话,你接触到的往往就是数据库事务了。
那数据库事务有什么作用呢?
简单来说,数据库事务可以保证多个对数据库的操作(也就是SQL语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:要么全部执行成功,要么全部不执行。
2、事务的四大特性ACID
- 原子性(Atomicity): 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部执行,要么都不执行。
- 一致性(consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的。
- 隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。
- 持久性(Durabllily): 一个事务被提交之后。它对数据库中数据的改变是永久的,即使数据库发生故障也不应该对其有任何影响。
3、事务的ACID如何保证?
- 事务的 隔离性 是通过 数据库锁和MVCC机制(多版本并发控制)实现的。
- 事务的 一致性 由 undo log来保证 :undo log是逻辑日志,记录了事务的 insert、update、deltete操作,回滚的时候做相反的delete、update、insert 操作来恢复数据。
- 事务的 原子性和持久性 由 redo log来保证:redolog被称作重做日志,是物理日志,事务提交的时候,必须先将事务的所有日志写入redo log持久化,到事务的提交操作才算完成。
注意: 只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说A、l、D是手段,C是目的。
4、事务的隔离级别有哪些?
事务的4个隔离级别:
- 读未提交(Read Uncommitted):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- 读已提交(Read Committed):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- 可重复读(Repeatable Read):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- 串行化(Serializable):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
MySQL默认的事务隔离级别是 可重复读(Repeatable Read),查看MySQL使用的引擎使用命令:select @@transaction_isolation;
。
5、什么是脏读、幻读、不可重复读?
- 脏读: 事务A、B交替执行,事务A读取到事务B未提交的数据,这就是脏读。
- 不可重复读: 在一个事务范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是不可重复读。
- 幻读: 事务A查询一个范围的结果集,另一个并发事务B往这个范围中插入或删除了数据,并静悄悄地提交,然后事务A再次查询相同的范围,两次读取得到的结果集不一样了,这就是幻读。
幻读产生的根本原因是: 当前读和快照读一起使用,如果同一个事务中只有快照读(读历史数据),那么永远不会出现幻读问题。
Q:不可重复读和幻读的区别?
- 不可重复读的重点是 内容修改或者记录减少 ,比如多次读取一条记录发现其中某些记录的值被修改。
- 幻读的重点在于 记录新增 ,比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。
幻读其实可以看作是不可重复读的一种特殊情况,单独区分幻读的原因主要是解决幻读和不可重复读的方案不一样。
举个例子: 执行delete和update操作的时候,可以直接对记录加锁,保证事务安全。而执行insert操作的时候,由于记录锁(Record Lock)只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁(Gap lock)。也就是说执行insert操作的时候需要依赖Next-Key Lock(Record Lock+Gap Lock)进行加锁来保证不出现幻读。
6、事务的隔离级别如何实现?
MySQL的隔离级别是 基于锁和MVCC机制共同实现的 。SERIALIZABLE隔离级别,是通过锁来实现的。除了SERIALIZABLE隔离级别,其他的隔离级别都是基于MVCC机制实现。
不过,SERIALIZABLE之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ在当前读情况下需要使用加锁读来保证不会出现幻读。
- 读未提交
读未提交,就不用多说了,采取的是 读不加锁原理 。
事务读不加锁,不阻塞其他事务的读和写。事务写阻塞其他事务写,但不阻塞其他事务读; - 读取已提交&可重复读
读取已提交和可重复读级别利用了ReadView和MVCC,也就是每个事务只能读取它能看到的版本(ReadView)。
READ COMMITTED:每次读取数据前都生成一个 ReadView 。
REPEATABLE READ:在第一次读取数据时生成一个 ReadView 。 - 串行化
串行化的实现采用的是读写都加锁的原理。
串行化的情况下,对于同一行事务,写会加写锁,读会加读锁。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
7、MVCC机制怎么实现的?
MVCC(Multi Version Concurrency Control),中文名是 多版本并发控制,简单来说就是通过维护数据历史版本,从而解决并发访问情况下的读一致性问题。关于它的实现,要抓住几个关键点,隐式字段、undo日志、版本链、快照读&当前读、Read View。
- 版本链
对于InnoDB存储引擎,每一行记录都有两个隐藏列 DB_TRX_ID、DB_ROLL_PTR 以及 DB_ROW_ID 。
DB_TRX_ID,最近修改事务id,每次修改时,都会把该事务id复制给DB_TRX_ID;
DB_ROLL_PTR,回滚指针,指向回滚段的undo日志。
DB_ROW_ID,隐藏主键,如果表有主键则不会生成该字段。
2.5 锁
1、MySQL中有哪些锁?
如果按锁粒度划分,有以下3种:
- 表锁: 开销小,加锁快;锁定力度大,发生锁冲突概率高,并发度最低;不会出现死锁。
- 行锁: 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。
- 页锁: 开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般。
如果按照兼容性,有2种: - 共享锁(S Lock) ,也叫读锁(read lock),相互不阻塞。
- 排他锁(X Lock) ,也叫写锁(write lock),排它锁是阻塞的,在一定时间内,只有一个请求能执行写入,并阻止其它锁读取正在写入的数据。
2、表锁和行锁的区别?
MylSAM仅仅支持表级锁(table-level locking),锁住整张表,这在并发写的情况下性非常差。
InnoDB不光支持表级锁(table-level locking),还支持行级锁(row-level locking),默认为行级锁。行级锁的粒度更小,仅对相关的记录上锁即可(对一行或者多行记录加锁),所以对于并发写入操作来说,InnoDB的性能更高。
表级锁和行级锁对比:
- 表级锁:MySQL中锁定粒度最大的一种锁,是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和InnoDB引擎都支持表级锁。
- 行级锁:MySQL中锁定粒度最小的一种锁,是针对索引字段加的锁,只针对当前操作的行记录进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
Q:行锁使用需要注意什么?
InnoDB的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。当我们执行update、delete语句时,如果where条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有行记录进行加锁。所以在使用行锁时最重要的就是 防止行锁失效,避免全表扫描。
3、共享锁和排他锁?
不论是表级锁还是行级锁,都存在共享锁(Share Lock,S锁)和排他锁(Exclusive Lock,X锁)这2类:
- 共享锁(s锁):又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
- 排他锁(X锁):又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。
注意: 排他锁和任何锁都不兼容,共享锁仅和共享锁兼容。
4、InnoDB中行锁如何实现?
InnoDB 的行锁的主要有3种:
- Record Lock记录锁
记录锁就是直接锁定某行记录。当我们使用唯一性的索引(包括唯一索引和聚簇索引)进行等值查询且精准匹配到一条记录时,此时就会直接将这条记录锁定。例如 select * from t where id =6 for update;
就会将id=6的记录锁定。
- Gap Lock间隙锁
间隙锁(Gap Locks)的间隙指的是两个记录之间逻辑上尚未填入数据的部分,是一个左开右开空间。
间隙锁就是锁定某些间隙区间的。当我们使用等值查询或者范围查询,并且没有命中任何一个record,此时就会将对应的间隙区间锁定。例如 select * from t where id > 1 and id < 6 for update;
就会将(1,6)区间锁定。
- Next-key Lock临键锁
临键指的是间隙加上它右边的记录组成的左开右闭区间。比如上述的(1,6]、(6,8]等。MySQL行锁默认使用临键锁。
临键锁就是记录锁(Record Locks)和间隙锁(Gap Locks)的结合,即除了锁住记录本身,还要再锁住索引之间的间隙。当我们使用范围查询,并且命中了部分record记录,此时锁住的就是临键区间。注意,临键锁锁住的区间会包含最后一个record的右边的临键区间。例如 select * from t where id > 5 and id <= 7 for update;
会锁住(4,7]、(7,+∞)。mysql默认行锁类型就是临键锁(Next-Key Locks)。当使用唯一性索引,等值查询匹配到一条记录的时候,临键锁(Next-Key Locks)会退化成记录锁;没有匹配到任何记录的时候,退化成间隙锁。
锁退化顺序: 临键锁 --> 记录锁 --> 间隙锁
注意: 间隙锁(Gap Locks)和临键锁(Next-Key Locks)都是用来解决幻读问题的,在读已提交(READ COMMITTED)隔离级别下,间隙锁(Gap Locks)和临键锁(Next-Key Locks)都会失效!
InnoDB的默认隔离级别RR(可重复读)是可以解决幻读问题发生的,主要有下面两种情况:
- 快照读(一致性非锁定读):由MVCC机制来保证不出现幻读。
- 当前读(一致性锁定读):使用Next-Key Lock进行加锁来保证不出现幻读。
幻读产生的根本原因是:快照读和当前读同时存在。
5、当前读和快照读的区别?
- 快照读 (一致性非锁定读)就是单纯的select语句,但不包括下面这两类select语句:
select ... for update、select ... lock in share mode
。
快照 即记录的历史版本,每行记录可能存在多个历史版本(多版本技术)。
快照读的情况下,如果读取的记录正在执行UPDATE/DELETE操作,读取操作不会因此去等待记录上排他锁(X锁)的释放,而是会去读取行的一个快照。 - 当前读 (一致性锁定读)就是给行记录加X锁或S锁。当前读即数据的当前版本。当前读的常见SQL语句有:
select ... for update、select ... lock in share mode、insert、update、delete
等。
6、意向锁是什么?
意向锁是一个表级锁,不要和插入意向锁搞混。意向锁的出现是为了支持 InnoDB的多粒度锁,它 解决的是表锁和行锁共存的问题 。
当我们需要给一个表加表锁的时候,我们需要根据去判断表中有没有数据行被锁定,以确定是否能加成功。意向锁是表级锁,共有2种:
- 意向共享锁(Intention Shared Lock,IS锁):事务有意向对表中的某些加共享锁(S锁),加共享锁前必须先取得该表的IS锁。
- 意向排他锁(Intention Exclusive Lock,IX锁):事务有意向对表中的某些记录加排他锁(X锁),加排他锁之前必须先取得该表的IX锁。
意向锁是由数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享/排他锁之前,InnoDB会先获取该数据行所在数据表的对应意向锁。==意向锁之间是互相兼容的。==意向锁和共享锁和排它锁互斥(这里指的是表级别的共享锁和排他锁,意向锁不会与行级的共享锁和排他锁互斥)。
假如没有意向锁,那么我们就得遍历表中所有数据行来判断有没有行锁;有了意向锁这个表级锁之后,则我们直接判断一次就知道表中是否有数据行被锁定了。
有了意向锁之后,要执行的事务 A 在申请行锁(写锁)之前,数据库会自动先给事务 A 申请表的意向排他锁。当事务 B 去申请表的互斥锁时就会失败,因为表上有意向排他锁之后事务 B 申请表的互斥锁时会被阻塞。
5、MySQL的乐观锁和悲观锁了解么?
- 悲观锁(Pessimistic Concurrency Control)
悲观锁认为被它保护的数据是极其不安全的,每时每刻都有可能被改动,一个事务拿到悲观锁后,其他任何事务都不能对该数据进行修改,只能等待锁被释放才可以执行。数据库中的行锁,表锁,读锁,写锁均为悲观锁。
- 乐观锁(Optimistic Concurrency Control)
乐观锁认为数据的变动不会太频繁。乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。
事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本v1与数据中最新的版本v2相对比,如果v1=v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时version会加1,以此来表明数据已被变动。如果v1不等于v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。不同于悲观锁,乐观锁通常是由开发者实现的。
6、MySQL死锁问题?
排查死锁的一般步骤如下:
- 查看死锁日志
show engine innodb status;
- 找出死锁 sql 语句。
- 分析 sql 加锁情况。
- 模拟死锁案发。
- 分析死锁日志。
- 分析死锁结果。
2.6 性能优化
1、读写分离机制?
读写分离的基本原理是 将数据库读写操作分散到不同的节点上 。架构图如下:
读写分离的基本实现是:
- 数据库服务器搭建主从集群,一主一从、一主多从都可以。
- 数据库主机负责读写操作,从机只负责读操作。
- 数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
- 业务服务器将写操作发给数据库主机,将读操作发给数据库从机。
2、主从复制原理?
当主节点执行了DDL、DML语句后,它会将数据的变更写入到自身的二进制日志(binlog)中。从节点中包含两个线程,一个是IOthred,主要负责去读取主节点的二进制日志(binlog),然后将读取到的二进制日志写入到自身的中继日志中;另一个SQLthread主要负责读取自身的中继日志,然后将中继日志中的数据变化反映到自身的数据变更(即根据中继日志来更新从节点中的数据)。
3、主从同步延迟怎么解决?
- 主从同步延迟的原因
一个服务器开放N个链接给客户端来连接的,这样会有大并发的更新操作, 但是从服务器的里面读取binlog的线程仅有一个,当某个SQL在从服务器上执行的时间稍长或者由于某个SQL要进行锁表就会导致主服务器的SQL大量积压,未被同步到从服务器里。这就导致了主从不一致,也就是主从延迟。
- 主从同步延迟的解决办法
1. 写操作后的读操作指定发给数据库主服务器。
例如,注册账号完成后,登录时读取账号的读操作也发给数据库主服务器。这种方式和业务强绑定,对业务的侵入和影响较大,如果哪个新来的程序员不知道这样写代码,就会导致一个bug。
2. 读从库失败后再读一次主库。
这就是通常所说的"二次读取",二次读取和业务无绑定,只需要对底层数据库访问的API进行封装即可,实现代价较小,不足之处在于如果有很多二次读取,将大大增加主机的读操作压力。例如,黑客暴力破解账号,会导致大量的二次读取操作,主机可能顶不住读操作的压力从而崩溃。
3. 关键业务读写操作全部指向主机,非关键业务采用读写分离。
例如,对于一个用户管理系统来说,注册+登录的业务读写操作全部访问主机,用户的介绍、爰好、等级等业务,可以采用读写分离,因为即使用户改了自己的自我介绍,在查询时却看到了自我介绍还是旧的,业务影响与不能登录相比就小很多,还可以忍受。
4、分库
垂直分库: 以表为依据,按照业务归属不同,将不同的表拆分到不同的库中,如将用户表、商品表、订单表拆分到不同库中。
水平分库: 以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中,如将用户表的1-100000条数据记录放到库1,100001-200000条记录放到库2。
5、分表
垂直分表: 以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
水平分表: 以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中。
6、水平分表的路由方式有哪些?
什么是路由呢? 就是数据应该分到哪一张表。
水平分表主要有3种路由方式:
-
范围路由:选取有序的数据列(例如,整形、时间戳等)作为路由的条件,不同分段分散到不同的数据库表中。
-
Hash路由:选取某个列(或者某几个列组合也可以)的值进行Hash运算,然后根据Hash结果分散到不同的数据库表中。
-
配置路由:配置路由就是路由表,用一张独立的表来记录路由信息。同样以订单id为例,我们新增一张order_router表,这个表包含orderjd和tablejd两列 , 根据orderjd就可以查询对应的table_id。
7、不停机扩容怎么实现?
· 第一阶段:在线双写,查询走老库
- 建立好新的库表结构,数据写入旧库的同时,也写入拆分的新库。
- 数据迁移,使用数据迁移程序,将旧库中的历史数据迁移到新库。
- 使用定时任务,新旧库的数据对比,把差异补齐。
· 第二阶段:在线双写,查询走新库
- 完成了历史数据的同步和校验。
- 把对数据的读切换到新库。
· 第三阶段:旧库下线
- 旧库不再写入新的数据。
- 经过一段时间,确定旧库没有请求之后,就可以下线老库。
8、常见的分库分表中间件有哪些?
Sharding-JDBC、MyCat
9、分库分表会带来什么问题?
· 从分库的角度来讲
- 事务的问题
使用关系型数据库,有很大一点在于它保证事务完整性。而分库之后单机事务就用不上了,必须使用分布式事务来解决。 - 跨库JOIN问题
在一个库中的时候我们还可以利用JOIN来连表查询,而跨库了之后就无法使用JOIN了。
此时的解决方案就是 在业务代码中进行关联 ,也就是先把一个表的数据查出来,然后通过得到的结果再去查另一张表,然后利用代码来关联得到最终的结果。
这种方式实现起来稍微比较复杂,不过也是可以接受的。
还有可以 适当的冗余一些字段 。比如以前的表就存储一个关联ID,但是业务时常要求返回对应的Name或者其他字段。这时候就可以把这些字段冗余到当前表中,来去除需要关联的操作。
还有一种方式就是 数据异构 ,通过binlog同步等方式,把需要跨库join的数据异构到ES等存储结构中,通过ES进行查询。
· 从分表的角度来看
- 跨节点的count、order by、group by以及聚合函数问题
只能由业务代码来实现或者用中间件将各表中的数据汇总、排序、分页然后返回。 - 数据迁移,容量规划,扩容等问题
数据的迁移,容量如何规划,未来是否可能再次需要扩容,等等,都是需要考虑的问题。 - ID 问题
数据库表被切分后,不能再依赖数据库自身的主键生成机制,所以需要一些手段来保证全局主键唯一。
① 还是自增,只不过 自增步长设置一下。比如现在有三张表,步长设置为3,三张表ID初始值分别是1、2、3。这样第一张表的ID增长是1、4、7。第二张表是2、5、8。第三张表是3、6、9,这样就不会重复了。
② UUID,这种最简单,但是不连续的主键插入会导致严重的页分裂,性能比较差。
③ 分布式 ID,比较出名的就是Twitter开源的sonwflake雪花算法。
10、百万级别以上的数据如何删除?
关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加、修改、删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。
所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。
① 想要删除百万数据的时候可以先删除索引。
② 然后删除其中无用数据。
③ 删除完成后重新创建索引创建索引也非常快。
11、百万千万级大表如何添加字段?
当线上的数据库数据量到达几百万、上千万的时候,加一个字段就没那么简单,因为可能会长时间锁表。大表添加字段,通常有这些做法:
- 通过中间表转换
创建一个临时的新表,把旧表的结构完全复制过去,添加字段,再把旧表数据复制过去,删除旧表,新表命名为旧表的名称,这种方式可能会丢掉一些数据。 - 用pt-online-schema-change
pt-online-schema-change是percona公司开发的一个工具,它可以在线修改表结构,它的原理也是通过中间表。 - 先在从库添加,再进行主从切换
如果一张表数据量大且是热表(读写特别频繁),则可以考虑先在从库添加,再进行主从切换,切换后再将其他几个节点上添加字段。
12、MySQL数据库CPU飙升,怎么处理?
排查过程:
- 使用top命令观察,确定是mysqld(mysql的守护线程)导致还是其他原因。
- 如果是mysqld(mysql的守护线程)导致的,show processlist,查看session情况,确定是不是有消耗资源的sql在运行。
- 找出消耗高的sql,看看执行计划是否准确,索引是否缺失,数据量是否太大。
处理:
- kill掉这些线程 (同时观察cpu使用率是否下降)。
- 进行相应的调整(比如说加索引、改sql、改内存参数)。
- 重新跑这些SQL。
其他情况:
也有可能是每个sql消耗资源并不多,但是突然之间,有大量的session连进来导致cpu飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等。
13、能否使用MySQL存储文件(如图片等)?
可以是可以,直接存储文件对应的二进制数据即可。不过,还是建议不要在数据库中存储文件,会严重影响数据库性能,消耗过多存储空间。
可以选择使用云服务厂商提供的开箱即用的文件存储服务,成熟稳定,价格也比较低,也可以选择自建文件存储服务,实现起来也不难,基于FastDFS、Minlo(推荐)等开源项目就可以实现分布式文件服务。
数据库只存储文件地址信息,文件由文件存储服务负责存储。
14、MySQL如何存储IP地址?
可以将IP地址转换成整型数据存储,性能更好,占用空间也更小。
MySQL提供了两个方法来处理ip地址:
- INET_ATON():把ip转为无符号整型(4-8位)。
- INET_NTOA():把整型的ip转为地址。
插入数据前,先用TNET_ATON()把ip地址转为整型,显示数据时,使用INET_MTOA()把整型的ip地址转为地址显示即可。
14、慢SQL如何定位?
- 慢查询日志:开启MySQL的慢查询日志,再通过一些工具比如 mysqldumpslow去分析对应的慢查询日志,当然现在一般的云厂商都提供了可视化的平台。
- 服务监控:可以在业务的基建中加入对慢SQL的监控,常见的方案有字节码插桩、连接池扩展、ORM框架过程,对服务运行中的慢SQL进行监控和告警。
15、如何优化慢SQL语句?
慢 SQL 的优化,主要从两个方面考虑,SQL 语句本身的优化,以及数据库设计的优化。
16、怎么看执行计划(explain),其中字段的含义?
explain是sql优化的利器,除了优化慢sql,平时的sql编写,也应该先explain,查看一下执行计划,看看是否还有优化的空间。
直接在select语句之前增加 explain 关键字,就会返回执行计划的信息。
2.7 索引
1、什么是索引?有何作用?
索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有:B树,B+树和Hash。
索引的作用就相当于书的目录。打个比方:我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
2、索引的优缺点?
- 优点
① 使用索引可以大大加快数据的检索速度(大大减少检索的数据量),这也是创建索引的最主要的原因。
② 通过创建唯—性索引,可以保证数据库表中每一行数据的唯—性。 - 缺点
① 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低SQL执行效率。
② 索引需要使用物理文件存储,也会耗费一定空间。
3、索引的分类有哪些?
- 主键索引: InnoDB主键是默认的索引,数据列不允许重复,不允许为null,一个表只能有一个主键。
- 唯一索引: 数据列不允许重复,允许为null值,一个表允许多个列创建唯一索引。
- 普通索引: 基本的索引类型,没有唯一性的限制,允许为null值。
- 组合索引: 多列值组成一个索引,用于组合搜索,效率大于索引合并。
4、创建索引需要注意的点?
索引虽然是sql性能优化的利器,但是索引的维护也是需要成本的,所以创建索引,也要注意:
① 索引应该建在查询应用频繁的字段,即在用于where判断、order排序和 join的(on)字段上创建索引。
② 索引的个数应该适量,索引需要占用空间;更新时候也需要维护。
③ 区分度低的字段,例如性别,不要建索引。离散度太低的字段,扫描的行数降低的有限。
④ 频繁更新的值,不要作为主键或者索引。维护索引文件需要成本;还会导致页分裂,IO次数增多。
⑤ 组合索引把散列性高(区分度高)的值放在前面,为了满足最左前缀匹配原则。
⑥ 创建组合索引,而不是修改单列索引。组合索引代替多个单列索引(对于单列索引,MySQL基本只能使用一个索引,所以经常使用多个条件查询时更适合使用组合索引)。
⑦ 过长的字段,使用前缀索引。当字段值比较长的时候,建立索引会消耗很多的空间,搜索起来也会很慢。我们可以通过截取字段的前面一部分内容建立索引,这个就叫前缀索引。
⑧ 不建议用无序的值(例如身份证、UUID)作为索引。当主键具有不确定性,会造成叶子节点频繁分裂,出现磁盘存储的碎片化。
5、索引什么时候会失效?
- 模糊查询以 “%” 开头索引失效,因为不确定 % 的值。
- 使用 or 的时候会失效,如果使用 or 要求条件两边都要有索引,才会使用索引,如果其中一边有一个字段没有索引,那么另一个字段上的索引也会失效。
- 使用复合索引的时候,没有使用第一索引列查找(违背最左前缀原则)。
- 在where当中索引列参加了运算,索引失效。
- 在where列中使用了函数。
- 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。
- 如果mysql估计使用全表扫描要比使用索引快,则不使用索引。
6、什么情况下不适合用索引?
- 数据量比较少的表不适合加索引
- 更新比较频繁的字段也不适合加索引
- 离散低的字段不适合加索引(如性别)
7、MySQL中索引为什么选用B+树?
-
Q:为什么不用普通二叉树?
普通二叉树存在退化的情况,如果它退化成链表,相当于全表扫描。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。 -
Q:为什么不用平衡二叉树呢?
读取数据的时候,是从磁盘读到内存。如果树这种数据结构作为索引,那每查找一次数据就需要从磁盘中读取一个节点,也就是一个磁盘块,但是平衡二叉树可是每个节点只存储一个键值和数据的,如果是B+树,可以存储更多的节点数据,树的高度也会降低,因此读取磁盘的次数就降下来啦,查询效率就快。 -
Q:为什么不用B树呢?
B+相比较B树,有这些优势: -
它是B树的变种,B树能解决的问题,B+树都能解决。(B树解决的两大问题:每个节点存储更多关键字;路数更多。)
-
扫库、扫表能力更强。
如果我们要对表进行全表扫描,只需要遍历叶子节点就可以了,不需要遍历整棵 B+树拿到所有的数据。 -
B+树的磁盘读写能力相对于B树来说更强,IO次数更少。
根节点和枝节点不保存数据区,所以一个节点可以保存更多的关键字,一次磁盘加载的关键字更多,IO次数更少。 -
排序能力更强。因为叶子节点上有下一个数据区的指针,数据形成了链表。
-
效率更加稳定。B+树永远是在叶子节点拿到数据,所以IO次数是稳定的。
8、Hash索引和B+树索引的区别?
- B+树可以进行范围查询√,Hash索引不能。
- B+树支持联合索引的最左前缀原则√,Hash索引不支持。
- B+树支持order by排序√,Hash索引不支持。
- B+树使用like进行模糊查询√的时候,like后面(比如%开头)的话可以起到优化的作用,Hash索引根本无法进行模糊查询。
- Hash索引在等值查询上比B+树效率更高。
9、聚集索引和非聚集索引的区别?
聚集索引: 将数据存储和索引放到了一块,索引结构的叶子节点保存了行数据,必须有,只能有一个。
二级索引(非聚集索引): 将数据和索引分开存储,索引结构的叶子节点关联的是对应的主键,可以存在多个。
首先理解聚簇索引不是一种新的索引,而是一种数据存储方式。聚簇表示数据行和相邻的键值紧凑地存储在一起。我们熟悉的两种存储引擎——MyISAM采用的是非聚簇索引,InnoDB采用的是聚簇索引。
-
索引的数据结构是树,聚簇索引的索引和数据存储在一棵树上,树的叶子节点就是数据,非聚簇索引索引和数据不在一棵树上。
-
一个表中只能有一个聚簇索引,而非聚簇索引一个表可以存在多个。
-
聚簇索引,索引中键值的逻辑顺序决定了表中相应行的物理顺序;索引、索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。
-
聚簇索引:物理存储按照索引排序;非聚集索引:物理存储不按照索引排序;
Q:聚集索引的选取规则?
①如果存在主键,主键索引就是聚集索引。
②如果不存在主键,将使用第一个唯一(UNIQUE)唯一索引作为聚集索引。
③ 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
10、回表了解么?
在InnoDB存储引擎里,利用非聚集索引查询,先通过非聚集索引找到聚集索引的键值,再通过聚集索引值查找聚集索引里面是否有符合要求的数据,它比基于聚集索引的查询多扫描了一棵索引树,这个过程就叫 回表。(简单的说,回表就是利用非聚集索引,先找到id值,再通过id值去查询对应的数据。)
例如:select \* from user where name = ‘张三’;
11、覆盖索引了解么?
在辅助索引里面,不管是单列索引还是联合索引,如果select的数据列只用辅助索引中就能够取得,不用去查主键索引,这时候使用的索引就叫做覆盖索引,避免了回表。
比如,select name from user where name = ‘张三’;
12、什么是最左前缀原则?
最左匹配原则: 在InnoDB的联合索引中,查询的时候只有匹配了前一个/左边的值之后,才能匹配下一个。
根据最左匹配原则,我们创建了一个组合索引,如(a1,a2,a3),相当于创建了(a1)、(a1,a2)和(a1,a2,a3)三个索引。
Q:为什么不从最左开始查,就无法匹配呢?
比如有一个user表,我们给name和age建立了一个组合索引。
alter table user add index comidx_name_phone (name,age);
组合索引在B+树中是复合的数据结构,它是按照从左到右的顺序来建立搜索树的 (name在左边,age在右边)。
从这张图可以看出来,name是有序的,age是无序的。当name相等的时候, age才是有序的。
这个时候我们使用 where name=‘张三‘ and age =‘20‘
去查询数据的时候,B+树会优先比较name来确定下一步应该搜索的方向,往左还是往右。如果name相同的时候再比较age。但是如果查询条件没有name,就不知道下一步应该查哪个节点,因为建立搜索树的时候name是第一个比较因子,所以就没用上索引。
13、什么是索引下推优化?
索引条件下推优化(Index Condition Pushdown(ICP))是MySQL5.6添加的,用于优化数据查询。
- 不使用索引条件下推优化时,存储引擎通过索引检索到数据,然后返回给 MySQL Server,MySQL Server进行过滤条件的判断。
- 当使用索引条件下推优化时,如果存在某些被索引的列的判断条件时,MySQL Server将这一部分判断条件下推给存储引擎,然后由存储引擎通过判断索引是否符合MySQL Server传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器。
例如一张表,建了一个联合索引(name, age),查询语句:select * from t_user where name like '张%' and age=10;
,由于name使用了范围查询,根据最左匹配原则:不使用索引条件下推优化,引擎层查找到name like '张%'的数据,再由Server层去过滤age=10这个条件,这样一来,就回表了两次,浪费了联合索引的另外一个字段age。
但是,使用了索引下推优化,把where的条件放到了引擎层执行,直接根据name like ‘张%’ and age=10的条件进行过滤,减少了回表的次数。
索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。
14、索引底层数据结构
· Hash表
哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。
为何能够通过 key 快速取出 value呢? 原因在于 哈希算法(也叫散列算法)。通过哈希算法,我们可以快速找到 key 对应的 index,找到了 index 也就找到了对应的 value。
但是!哈希算法有个 Hash冲突 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 链地址法。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 HashMap 就是通过链地址法来解决哈希冲突的。不过,JDK1.8 以后HashMap为了减少链表过长的时候搜索时间过长引入了红黑树。
为了减少 Hash 冲突的发生,一个好的哈希函数应该“均匀地”将数据分布在整个可能的哈希值集合中。
Q:为什么MySQL不使用Hash表作为索引的数据结构呢?
- Hash冲突问题:我们上面也提到过Hash冲突了,不过对于数据库来说这还不算最大的缺点。
- Hash索引不支持顺序和范围查询(Hash索引不支持顺序和范围查询是它最大的缺点:假如我们要对表中的数据进行排序或者进行范围查询,那Hash索引可就不行了。
· B树&B+树
B树全称为 多路平衡查找树 ,B+树是B树的一种变体。
Q:B树和B+树两者有何异同呢?
- B树的所有节点既存放键(key)也存放数据(data),而B+树只有叶子节点存放key和data,其他内节点只存放key。
- B树的叶子节点都是独立的,B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
15、索引类型
- 主键索引
数据表的主键列使用的就是主键索引。
一张数据表有只能有一个主键,并且主键不能为null,不能重复。
在MysQL的InnoDB的表中,当没有显示的指定表的主键时,InnoDB会自动先检查表中是否有唯一索引且不允许存在null值的字段,如果有,则选择该字段为默认的主键,否则InnoDB将会自动创建一个6Byte的自增主键。
- 二级索引(非聚集索引)
- 聚集索引和非聚集索引
Q:非聚集索引一定会回表查询么?
- 联合索引
16、MySQL中查询优化?
- 应尽量避免在 where 子句中使用 !=或<> 操作符,否则将放弃使用索引而进行全表扫描。
- 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。
- 尽量避免在 where 子句中使用 or 来连接条件,否则将放弃使用索引而进行全表扫描(or 只有左右字段都存在索引时才会走索引,只要一边没有索引,都会导致索引失效)。
- 避免在查询中使用模糊匹配,如"%c"。
- in 和 not in 也要慎用,否则会导致全表扫描。
- 如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。
- 应尽量避免在 where 子句中对字段进行表达式操作,这将导致放弃使用索引而进行全表扫描。
- 应尽量避免在where子句中对字段进行函数操作,这将导致放弃使用索引而进行全表扫描。
- 不要在 where 子句中的 “=” 左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
- 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致(最左前缀法则)。
- 不要写一些没有意义的查询。
- 很多时候用 exists 代替 in 。
- 并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引。
- 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
- 应尽可能的避免更新索引数据列,因为索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
- 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会 逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
- 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
- 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
- 尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
- 避免频繁创建和删除临时表,以减少系统表资源的消耗。
- 临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,较好使 用导出表。
- 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
- 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
- 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
- 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
- 与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
- 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONEINPROC 消息。
- 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
- 尽量避免大事务操作,提高系统并发能力。
2.8 日志
1、MySQL日志文件有哪些?作用是什么?
- 错误日志(error log): 错误日志文件对 MySQL 的启动、运行、关闭过程进行了记录,能帮助定位 MySQL 问题。
- 慢查询日志(slow query log): 慢查询日志是用来记录执行时间超过 long_query_time 这个变量定义的时长的查询语句。通过慢查询日志,可以查找出哪些查询语句的执行效率很低,以便进行优化。
- 一般查询日志(general log): 一般查询日志记录了所有对 MySQL 数据库请求的信息,无论请求是否正确执行。
- 二进制日志(bin log): 关于二进制日志,它记录了数据库所有执行的 DDL 和 DML 语句(除了数据查询语句 select、show 等),以事件形式记录并保存在二进制文件中。
- 重做日志(redo log): 重做日志至关重要,因为它们记录了对于 InnoDB 存储引擎的事务日志。(InnoDB特有的日志)
- 回滚日志(undo log): 回滚日志同样也是 InnoDB 引擎提供的日志,顾名思义,回滚日志的作用就是对数据进行回滚。当事务对数据库进行修改,InnoDB 引擎不仅会记录 redo log,还会生成对应的 undo log 日志;如果事务执行失败或调用了 rollback,导致事务需要回滚,就可以利用 undo log 中的信息将数据回滚到修改之前的样子。(InnoDB特有的日志)
2、redo log 日志
redo log(重做日志)是InnoDB存储引擎独有的,它**让MySQL拥有了崩溃恢复能力。**
比如MySQL实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。
3、binlog日志
4、两阶段提交
5、undo log 日志
6、binlog 和 redo log的区别?
- binlog 会记录所有与数据库有关的日志记录,包括InnoDB、MyISAM等存储引擎的日志,而redo log 只记 InnoDB 存储引擎的日志。
- 记录的内容不同,binlog记录的是关于一个事务的具体操作内容,即该日志是逻辑日志。而 redo log 记录的是关于每个页(Page)的更改的物理情况。
- 写入的时间不同,binlog 仅在事务提交前进行提交,也就是只写磁盘一次。而在事务进行的过程中,却不断有 redo ertry 被写入 redo log 中。
- 写入的方式也不相同,redo log 是循环写入和擦除,binlog 是追加写入,不会覆盖已经写的文件。
7、一条更新语句怎么执行?
更新语句的执行是Server层和引擎层配合完成,数据除了要写入表中,还要记录相应的日志。
- 执行器先找引擎获取ID=2这一行数据。ID是主键,存储引擎检索数据,找到这一行。
如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。 - 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在就是N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于**prepare状态**。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的binlog,并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。
从上图可以看出,MySQL在执行更新语句的时候,在服务层进行语句的解析和执行,在引擎层进行数据的提取和存储;同时在服务层对binlog进行写入,在 InnoDB 内进行redo log的写入。
不仅如此,在对redo log写入时有两个阶段的提交,一是binlog写入之前prepare状态的写入,二是binlog写入之后commit状态的写入。
8、为什么需要两阶段提交?
我们可以假设不采用两阶段提交的方式,而是采用“单阶段”进行提交,即要么先写入redo log,后写入binlog;要么先写入binlog,后写入redo log。这两种方式的提交都会导致原先数据库的状态和被恢复后的数据库的状态不一致。
-
先写入redo log,后写入binlog
在写完redo log之后,数据此时具有crash-safe能力,因此系统崩溃,数据会恢复成事务开始之前的状态。但是,若在redo log写完时候,binlog写入之前,系统发生了宕机。此时binlog没有对上面的更新语句进行保存,导致当使用 binlog进行数据库的备份或者恢复时,就少了上述的更新语句。从而使得id=2这一行的数据没有被更新。
-
先写入binlog,后写入redo log
写完binlog之后,所有的语句都被保存,所以通过binlog复制或恢复出来的数据库中id=2这一行的数据会被更新为a=1。但是如果在redo log写入之前,系统崩溃,那么redo log中记录的这个事务会无效,导致实际数据库中id=2这一行的数据并没有更新。
redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
9、redo log日志怎么刷入磁盘?
redo log 的写入不是直接落到磁盘,而是在内存中设置了一片称之为 redo log buffer 的连续内存空间,也就是 redo日志缓冲区。
Q:什么时候会刷盘?
- log buffer 空间不足时
log buffer的大小是有限的,如果不停的往这个有限大小的log buffer里塞入日志,很快它就会被填满。如果当前写入log buffer的redo日志量已经占满了 log buffer总容量的大约一半左右,就需要把这些日志刷新到磁盘上。 - 事务提交时
在事务提交时,为了保证持久性,会把log buffer中的日志全部刷到磁盘。注意,这时候,除了本事务的,可能还会刷入其它事务的日志。 - 后台线程输入
有一个后台线程,大约每秒都会刷新一次log buffer中的redo log到磁盘。 - 正常关闭服务器时
- 触发checkpoint规则
重做日志缓存、重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),块的大小是固定的512字节。我们的redo log 它是固定大小的,可以看作是一个逻辑上的log group,由一定数量的log block 组成。
它的写入方式是从头到尾开始写,写到末尾又回到开头循环写。
其中有两个标记位置:
write pos是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。
checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到磁盘。
当write_pos追上checkpoint时,表示redo log日志已经写满。这时候就不能接着往里写数据了,需要执行checkpoint规则腾出可写空间。
所谓的checkpoint 规则,就是 checkpoint 触发后,将buffer中日志页都刷到磁盘。