MySQL
基础
概述
数据库:存储数据的仓库,数据是有组织的进行存储,简称为DataBase(DB)
数据库管理系统:操纵和管理数据库的大型软件,简称为DataBase Management System(DBMS)
SQL:操作关系型数据库的编程语言,定义了一套操作关系型数据库系统统一标准,简称为Structed Query Language(SQL)
- 启动
1 | net start mysql80 |
- 停止
1 | net stop mysql80 |
- 客户端连接
方法一:MySQL提供的客户端命令行工具
方法二:系统自带的命令行工具执行指令
1 | mysql [-h 127.0.0.1] [-P 3306] -u root -p |
注意:使用第二种方式时,需要配置PATH环境变量
关系型数据库(RDBMS)
概念:建立在关系模型基础上,由多张相互连接的二维表组成的数据库
特点:
- 使用表存储数据,格式统一,便于维护
- 使用SQL语言操作,标准统一,使用方便
数据模型

SQL
SQL通用语法
- SQL语句可以单行或者多行书写,以分号结尾
- SQL语句可以使用空格/缩进来增强语句的可读性
- MySQL数据库的SQL语句不区分大小写,关键字建议大写
- 注释:单行注释(–注释内容或# 注释内容(MySQL独有)) 多行注释(/* 注释内容 */)
SQL分类
| 分类 | 全称 | 说明 |
|---|---|---|
| DDL | Data Definnition Language | 数据定义语言,用来定义数据库对象(数据库,表,字段) |
| DML | Data Manipulation Language | 数据操作语言,用来对数据库表中的数据进行增删改 |
| DQL | Data Query Language | 数据查询语言,用来查询数据库表的记录 |
| DCL | Data Control Language | 数据控制语言,用来创建数据库用户,控制数据库的访问权限 |
DDL
数据库操作
查询
- 查询所有数据库
1 | SHOW DATABASES; |
- 查询当前数据库
1 | SELECT DATABASE(); |
创建
1 | CREATE DATABASE [IF NOT EXISTS] 数据库名 [DEFAULT CHARSET 字符集] [COLLATE 排列规则]; |
删除
1 | Drop DATABASE[IF EXISTS] 数据库名; |
使用
1 | USE 数据库名; |
表操作
查询当前数据库中所有表
1 | SHOW TABLES; |
查询表结构
1 | DESC 表名; |
查询指定表的建表语句
1 | SHOW CREATE TABLE 表名; |
创建
1 | CREATE TABLE 表名{ |
注意:[…]位=为可选参数,最后一个字段后面没有逗号
数据类型
MySQL中的数据类型有很多,主要分为三类:数值类型、字符串类型、日期时间类型。
修改
- 添加字段
1 | ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT 注释] [约束]; |
- 修改数据类型
1 | ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度) |
- 修改字段名和字段类型
1 | ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释] [约束]; |
- 删除字段
1 | ALTER TABLE 表名 DROP 字段名; |
- 修改表名
1 | ALTER TABLE 表名 RENAME TO 新表名; |
删除
- 删除表
1 | DROP TABLE [IF EXISTS] 表名; |
- 删除指定表,并重新创建该表
1 | TRUNCATE TABLE 表名; |
注意:在删除表时,表中的全部数据也会被删除
DML
DML英文全称是Data Manipulation Language(数据操作语言),用来对数据库中表的数据记录进行增删改操作。
- 添加数据 INSERT
- 修改数据 UPDATE
- 删除数据 DELETE
添加数据
- 给指定字段添加数据
1 | INSERT INTO 表名(字段名1,字段名2,...) VALUES(值1,值2,...); |
- 给全部字段添加数据
1 | INSERT INTO 表名 VALUES(值1,值2,...); |
- 批量添加数据
1 | INSERT INTO 表名(字段名1,字段名2,...) VALUES(值1,值2,...),(值1,值2,...),(值1,值2,...); |
1 | INSERT INTO 表名 VALUES(值1,值2,...),(值1,值2,...),(值1,值2,...); |
注意:
- 插入数据时,指定的字段顺序需要与值得顺序是一一对应的
- 字符串和日期型数据应该包含在引号中
- 插入的数据大小,应该在字段的规定范围内
修改数据
1 | UPDATE 表名 SET 字段1 = 值1 ,字段2 = 值2, ...[WHERE 条件]; |
注意:修改语句的条件可以有,也可以没有,如果没有条件,则会修改整张表的所有数据
删除数据
1 | DELETE FROM 表名 [WHERE 条件]; |
注意:
- DELETE语句的条件可以有,也可以没有,如果没有条件,则会删除所有整张表的所有数据
- DELETE语句不能删除某一个字段的值(可以使用UPDATE)
DQL
DQL全称是Data Query Lanuage(数据查询语言),数据查询语言,用来查询数据库表的记录
查询关键字:SELECT
语法:
1 | SELECT |
基础查询
- 查询多个字段
1 | SELECT 字段1,字段2,字段3... FROM 表名; |
1 | SELECT * FROM 表名; |
- 设置别名
1 | SELECT 字段1 [AS 别名1], 字段2 [AS 别名2] ... FROM 表名; |
- 去除重复记录
1 | SELECT DISTINCT 字段列表 FROM 表名; |
条件查询
语法
1 | SELECT 字段列表 FROM 表名 WHERE 条件列表; |
条件
| 运算符 | 功能 |
|---|---|
| > | 大于 |
| >= | 大于等于 |
| < | 小于 |
| <= | 小于等于 |
| = | 等于 |
| <>或!= | 不等于 |
| BETWEEN… AND… | 在某个范围之内(含最小、最大值) |
| IN(…) | 在in之后的列表中的值,多选一 |
| LIKE 占位符 | 模糊匹配(_匹配单个字符,%匹配任意字符) |
| IS NULL | 是NULL |
| AND 或 && | 并且(多个条件同时成立时) |
| OR 或 || | 或者(多个条件任意一个成立时) |
| NOT 或 ! | 非,不是 |
聚合函数
将一列数据作为一个整体,进行纵向计算
常见聚合函数
| 函数 | 功能 |
|---|---|
| count | 统计数量 |
| max | 最大值 |
| min | 最小值 |
| avg | 平均值 |
| sum | 求和 |
语法
1 | SELECT 聚合函数 (字段列表) FROM 表名; |
注意:所有null值不参与所有聚合函数运算
分组查询
语法
1 | SELECT 字段列表 FROM 表名 [where 条件] GROUP BY 分组字段名 [HAVING 分组后过滤条件]; |
where和having区别
- 执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组之后对结果进行过滤
- 判断条件不同:where不能对聚合函数进行判断,而having可以
注意
- 执行顺序:where > 聚合函数 > having
- 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段五任何意义
排序查询
语法
1 | SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1, 字段2 排序方式2; |
排序方式
- ASC:升序(默认值)
- DESC:降序
如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序
分页查询
语法
1 | SELECT 字段列表 FROM 表名 LIMIT 起始索引,查询记录数; |
注意
- 起始索引从0开始,起始索引 = (查询页码 - 1) *每页记录数
- 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL是LIMIT
- 如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10
执行顺序
1 | FROM |
DCL
DCL全称是Data Control Language(数据库控制语言),用来管理数据库用户、控制数据库的访问权限
管理用户
- 查询用户
1 | USE mysql; |
- 创建用户
1 | CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'; |
- 修改用户密码
1 | ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码'; |
- 删除用户
1 | DROP USER '用户名'@'主机名'; |
注意
- 主机名可以使用%适配
- 这类SQL开发人员操作的比较少,主要是DBA(Data Administrator 数据库管理员)使用
权限控制
MySQL中定义了很多权限,但是常见的就以下几种:
| 权限 | 说明 |
|---|---|
| ALL,ALL PRIVILEGES | 所有权限 |
| SELECT | 查询数据 |
| INSERT | 插入数据 |
| UPDATE | 修改数据 |
| DELETE | 删除数据 |
| ALTER | 修改表 |
| DROP | 删除数据库/表/视图 |
| CREATE | 创建数据库/表 |
- 查询权限
1 | SHOW GRANTS FOR '用户名'@'主机'; |
- 授予权限
1 | GRANTS 权限列表 ON 数据库名.表名 TO '用户名'@'主机'; |
- 撤销权限
1 | REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机'; |
注意
- 多个权限之间,使用逗号分隔
- 授权时,数据库名和表名可以使用*进行通配,代表所有
函数
函数 是指一段能够可以直接被另一段程序调用的程序或者代码
字符串函数
MySQL中内置了很多字符串函数,常用的几个如下:
| 函数 | 功能 |
|---|---|
| CONCAT(S1,S2,…Sn) | 字符串拼接,将S1、S2、…Sn拼接成一个字符串 |
| LOWER(str) | 将字符串str全部转化为小写字母 |
| UPPER(str) | 将字符串str全部转化为大写字母 |
| LPAD(str,n,pad) | 左填充,用字符串pad对str的左边进行填充,达到n个字符串长度 |
| RPAD(sre,n,pad) | 右填充,用字符串pad对str的右边进行填充,达到n个字符串长度 |
| TRIM(str) | 去掉字符串头部和尾部的空格 |
| SUBSTRING(str,start,len) | 返回从字符串start位置起的len个长度的字符串(索引从1开始) |
1 | SELECT 函数(参数); |
数值函数
常见的数值函数如下:
| 函数 | 功能 |
|---|---|
| CEIL(x) | 向上取整 |
| FLOOR(x) | 向下取整 |
| MOD(x,y) | 返回x/y的模 |
| RAND() | 返回0~1之内的随机数 |
| ROUND(x,y) | 求参数x的四舍五入的值,保留y位小数 |
日期函数
常见的日期函数如下:
| 函数 | 功能 |
|---|---|
| CURDATE() | 返回当前日期 |
| CURTIME() | 返回当前时间 |
| NOW() | 返回当前日期和时间 |
| YEAR(date) | 获取指定date的年份 |
| MONTH(date) | 获取指定date的月份 |
| DAY(date) | 获取指定date的日期 |
| DATE_ADD(date,INTERVAL expr type) | 返回上一个日期/时间值加上一个时间间隔expr后的时间值 |
| DATEDIFF(date1,date2) | 返回起始时间date1和结束时间date2之间的天数 |
流程函数
流程函数也是很常用的一类函数,可以在SQL语句中实现条件筛选,从而提高语句的效率
| 函数 | 功能 |
|---|---|
| IF(value,t,f) | 如果value为true,则返回t,否则返回f |
| IFNULL(value1,value2) | 如果value不为空,返回value1,否则返回value2 |
| CASE WHEN [val1] THEN [res1] … ELSE [default] END | 如果val1为true,返回res1,…否则返回default默认值 |
| CASE [expr] WHEN [val1] THEN [res1] … ELSE [default] END | 如果expr的值等于val1,返回res1,…否则返回default默认值 |
约束
概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据。
目的:保证数据库中数据的正确,有效性和完整性
分类:
| 约束 | 描述 | 关键字 |
|---|---|---|
| 非空约束 | 限制该字段的数据不能为null | NOT NULL |
| 唯一约束 | 保证该字段的所有数据都是唯一的,不重复的 | UNIQUE |
| 主键约束 | 主键是一行数据的唯一标识,要求非空且唯一 | PRIMARY KEY |
| 默认约束 | 保存数据时,如果未指定该字段的值,则采用默认值 | DEFAULT |
| 检查约束(8.0.16版本之后) | 保证字段值满足某一个条件 | CHECK |
| 外键约束 | 用来让两张表的数据之间建立连接,保证数据的一致性和完整性 | FOREIGN KEY |
注意:约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束,主键自增(auto -increment)
外键约束
语法
- 添加外键
1 | CREATE TABLE 表名( |
1 | ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表(主表列名); |
- 删除外键
1 | ALTER TABLE 表名 DROP FOREIGN KEY 外键名称; |
- 删除/更新行为
| 行为 | 说明 |
|---|---|
| NO ACTION | 当在父表中删除/更新记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新 |
| RESTRICT | 当在父表中删除/更新记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新 |
| CASCADE | 当在父表中删除/更新记录时,首先检查该记录是否有对应外键,如果有,则也删除/更新外键在子表中的记录 |
| SET NULL | 当在父表中删除对应记录时,首先检查该记录是否有对应外键,如果有,则设置子表中该外键值为null(这就要求该外键允许取null) |
| SET DEFAULT | 父表有变更时,子表将外键列设置成一个默认的值(Innodb不支持) |
1 | ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段) REFERENCES 主表名(主表字段名) ON UPDATE CASCADE ON DELETE CASCADE; |
多表查询
项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三类:一对多(多对一)、多对多、一对一
多表关系
- 一对多(多对一)
案例:部门与员工之间的关系
关系:一个部门对应多个员工,多个员工对应一个部门
实现:在多的一方建立外键,指向一的一方建立主键
- 多对多
案例:学生与课程之间的关系
关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
实现:建立第三张中间表,中间至少包含两个外键,分别关联两方主键
- 一对一
案例:用户与用户详情的关系
关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
多表查询分类
- 连接查询
- 内连接:相当于查询A、B交集部分数据
- 外连接:
- 左外连接:查询左表所有数据,以及两张表交集部分数据
- 右外连接:查询右表所有数据,以及两张表交集部分数据
- 自连接:当前表与自身的连接查询,自连接必须使用表别名
- 子查询
连接查询
内连接
查询语法
- 隐式内连接
1 | SELECT 字段列表 FROM 表1,表2 WHERE 条件 ...; |
- 显式内连接
1 | SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 连接条件 ...; |
内连接查询的是两张表交集的部分

外连接
查询语法
- 左外连接
1 | SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件 ...; |
相当于查询表1(左表)的所有数据 包含表1和表2交集部分的数据
- 右外连接
1 | SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件 ...; |
相当于查询表2(右表)的所有数据 包含表1和表2交集部分的数据
自连接
查询语法
1 | SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ...; |
自连接查询,可以是内连接查询,也可以是外连接查询
注意:在自连接查询时,必须要为表起别名,要不然我们不清楚所指定的条件、返回的字段,到底是哪一张表的字段
联合查询-union,union all
对于union查询,就是把多次查询的结果合并起来,形成一个新的查询结果集
1 | SELECT 字段列表 FROM 表A ... |
对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致
union all 会将全部的数据直接合并在一起,union 会对合并之后的数据去重
子查询
概念:SQL语句中嵌套SELECT语句,称为嵌套查询,又称子查询
1 | SELECT * FROM t1 where column1 = (SELECT column1 FROM t2); |
子查询外部的语句可以是INSERT / UPDATE / DELETE SELECT 的任何一个
根据子查询结果不同,分为以下四类:
- 标量子查询(子查询的结果为单个值)
- 列子查询(查询结果为一列)
- 行子查询(查询结果为一行)
- 表子查询(子查询结果为多行多列)
根据子查询位置:分为:WHERE之后、FROM之前、SELECT之后
标量子查询
子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式
常用的操作符:= <> >= > <= <
列子查询
子查询返回的结果是一列(可以是多行)
常用的操作符:IN、NOT IN、ANY、SOME、ALL
| 操作符 | 描述 |
|---|---|
| IN | 在指定的集合范围之内,多选一 |
| NOT IN | 不在指定集合范围内 |
| ANY | 子查询返回列表中,有任意一个条件满足即可 |
| SOME | 与ANY等同,使用SOME的地方都可以使用ANY |
| ALL | 子查询返回列表的所有值都必须满足 |
行子查询
子查询返回的结果是一行(可以是多列)
常用
的操作符:=、<>、IN、NOT IN
表子查询
子查询返回的结果是多行多列
常用的操作符:IN
事务
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功、要么同时失败
默认MySQL的事务是自动提交的,也就是说,当执行一条DML语句,MySQL会立即隐式的提交事务
事务操作
方式一
- 查看 / 设置事务提交方式
1 | SELECT @@autocommit; |
- 提交事务
1 | COMMIT; |
- 回滚事务
1 | ROLLBACK; |
方式二
- 开始事务
1 | START TRANSACTION 或 BEGIN; //开始事务 |
- 提交事务
1 | COMMIT; |
- 回滚事务
1 | ROLLBACK; |
事务四大特性(ACID)
原子性(Atomicty):事务是不可分割的最小操作单元,要么全部成功,要么全部失败
一致性(Consistency):事务完成时,必须使所有数据都保持一致状态
隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响下的独立环境下运行
持久性(Durability):事务一旦提交或者回滚,它对数据库中的数据的改变就是永久的
并发事务问题
| 问题 | 描述 |
|---|---|
| 脏读 | 一个事务读到另一个事务还没有提交的数据 |
| 不可重复读 | 一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读 |
| 幻读 | 一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了”幻影” |
事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read uncommitted | √ | √ | √ |
| Read committed | × | √ | √ |
| Repeatable Read(默认) | × | × | √ |
| Serializable | × | × | × |
1 | -- 查看事务隔离级别 |
注意:事务的隔离级别越高,数据越安全,但是性能越低
进阶
存储引擎
MySQL体系结构
- 连接层
最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限
- 服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析与优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等
- 引擎层
存储引擎真正地负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选择合适的引擎
- 存储层
主要是将数据存储在文件系统之上,并完成与存储引擎的交互
存储引擎
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型
- 在创建表时,指定存储引擎
1 | CREATE TABLE 表名( |
- 查看当前数据库支持的存储引擎
1 | SHOW ENGINES; |
InnoDB
InnoDB是一种兼顾可靠性和高性能的通用存储引擎,在MySQL5.5之后,InnoDB是默认的MySQL存储引擎
特点
- DML操作支持ACID模型,支持事务;
- 行级锁,提高并发访问性能;
- 支持外键FOREIGN KEY 约束,保证数据的完整性和正确性
文件
- xxx.ibd:xxx代表的是表名,InnoDB引擎的每一张表都会对应这样的一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引
- 参数:innodb_file_per_table
存储结构

MyISAM
MyISAM是MySQL早期的默认存储引擎
特点
- 不支持事务,不支持外键
- 支持表锁,不支持行锁
- 访问速度快
文件
- xxx.sdi:存储表结构信息
- xxx.MYD:存储数据
- xxx.MYI:存储索引
Memory
Memory引擎的表数据是存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时表或缓存使用
特点
- 内存存放
- hash索引(默认)
文件
- xxx.sdi:存储表结构信息
区别
| 特点 | InnoDB | MyISAM | Memory |
|---|---|---|---|
| 存储限制 | 64TB | 有 | 有 |
| 事务安全 | 支持 | - | - |
| 锁机制 | 行锁 | 表锁 | 表锁 |
| B+tree索引 | 支持 | 支持 | 支持 |
| Hash索引 | - | - | 支持 |
| 全文索引 | 支持(5.6版本之后) | 支持 | - |
| 空间使用 | 高 | 低 | N/A |
| 内存使用 | 高 | 低 | 中等 |
| 批量插入速度 | 低 | 高 | 高 |
| 支持外键 | 支持 | - | - |
存储引擎选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎,对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合
- InnoDB:是MySQL的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件上要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较适合的xuanz
- MyISAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求并不是很高,那么选择这个存储引擎是非常合适的
- MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性
索引
概述
索引(inedx)是帮助MySQL高效获取数据的**数据结构(有序)**。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现查找算法,这种数据结构就是索引。
- 优缺点
| 优势 | 缺点 |
|---|---|
| 提高数据检索的效率,降低数据库的IO成本 | 索引列也是要占用内存空间的 |
| 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 | 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率很低 |
索引结构
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下内容:
| 索引结构 | 描述 |
|---|---|
| B+ Tree索引 | 最常见的索引类型,大部分引擎都支持B+ 树索引 |
| Hash 索引 | 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询 |
| R- tree (空间索引) | 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
| Full-text (全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucence,Solr,ES |
| 索引 | InnoDB | MyISAM | MEMORY |
|---|---|---|---|
| B+ tree索引 | 支持 | 支持 | 支持 |
| Hash 索引 | 不支持 | 不支持 | 支持 |
| R- tree索引 | 不支持 | 支持 | 不支持 |
| Full-text索引 | 5.6版本后支持 | 支持 | 不支持 |
B-tree(多路平衡查找树)
以一颗最大度数为5(5阶)的b-tree为例(每个节点最多存储4个key,5个指针):

B+Tree
以一颗最大度数为4的b+tree为例:

相比较B-Tree的区别:
- 所有的数据都会出现在要叶子结点
- 叶子结点形成一个单向链表
MySQL的索引数据结构对经典的B+Tree进行了优化,在原B+Tree的基础上,增加了一个指向相邻叶子结点的链表指针,就形成了带有顺序指针的B+Tree,提高了区间访问的性能,利于排序

Hash
哈希索引就是采用一定的哈希算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中
如果两个或多个键值,映射到一个相同的槽位上时,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决
索引特点
- hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,…)
- 无法利于索引完成排序操作
- 查询效率高,通常只需要一次检索就可以了,效率通常要高于B+Tree索引
在MySQL中,支持hash索引的是Memory引擎,而InnoDB中具有自适应hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的
索引分类
| 分类 | 含义 | 特点 | 关键字 |
|---|---|---|---|
| 主键索引 | 针对表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
| 唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
| 常规索引 | 快速定位特定数据 | 可以有多个 | |
| 全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
| 分类 | 含义 | 特点 |
|---|---|---|
| 聚集索引(Clustered Index) | 将数据存储和索引放到了一块,索引结构的叶子结点保存了行数据 | 必须有,而且只有一个 |
| 二级索引(Secondary Index) | 将数据和索引分开存储,索引结构的叶子结点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
- 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引
索引语法
- 创建索引
1 | CREATE [UNIQUE | FULLTEXT] INDEX index_name on table_name (index_col,name,...); |
- 查看索引
1 | SHOW INDEX FROM table_name; |
- 删除索引
1 | DROP INDEX index_name ON table_name; |
SQL性能分析
- SQL执行频率
MySQL客户端连接成功后,通过show[session|global] status命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次;
1 | SHOW GLOBAL STATUS LIKE 'COM_____'; -- 一个下划线代表一个字符 |
- 慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
1 | # 开启MySQL慢日志查询开关 |
配置完毕后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息/var/lib/mysql/localhost-slow.log
- profile详情
show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了,通过have_profiling参数,能够看到当前MySQL是否支持profile操作:
1 | SELECT @@have_profiling; |
默认profiling是关闭的,可以通过set语句在session / global级别开启profiling:
1 | SELECT @@profiling; -- 查询是否开始 |
执行一系列的业务SQL操作,然后通过以下指令查看指令的执行耗时:
1 | # 查看每一条SQL的耗时基本情况 |
- explain执行计划
EXPLAIN或者DESC命令获取MySQL如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序
语法:
1 | # 直接在select语句前加上关键字explain / desc |
EXPLAIN执行计划各字段含义:
- id
select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)
- select_type
表示SELECT的类型,常见的取值有SIMPLE(简单表,即不能使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询等)
- type
表示连接类型,性能由好到差的连接类型为NULL、system、const、eq_ref、ref、range、index、all
- possible_key
显示可能应用在这张表上的索引,一个或者多个
- key
实际使用的索引,如果为NULL,则没有使用索引
- key_len
表示索引中使用的字节数,该值为索引字段的最大可能长度,并非实际长度,在不损失精确性的前提下,长度越短越好
- rows
MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并总是准确的
- filtered
表示返回结果的行数占需读取行数的百分比,filtered的值越大越好
使用规则
- 验证索引效率
在未建立索引之前,执行如下SQL语句,查看SQL的耗时
1 | SELECT * FROM tb_sku WHERE sn = '1000000000314001'; |
针对字段创建索引
1 | create index idx_sku_sn on tb_sku(sn); |
然后再次执行相同的SQL语句,再次查看SQL的耗时
1 | SELECT * FROM tb_sku WHERE sn = '1000000000314001'; |
最左前缀法则
如果索引了多列(联合索引),要遵循最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃了某一列,索引将部分失效(后面的字段索引失效)
索引失效情况
- 索引列运算
不要在索引列上进行运算操作,索引将失效
- 模糊查询
如果仅仅是尾部模糊查询,索引将不会失效;如果是头部模糊查询,索引将会失效
- or连接的条件
用or分割开的条件,如果or前的条件中的列有索引,而后面的列没有索引,那么涉及的索引都不会被使用
1 | explain select * from tb_user where id = 10 or age = 20; |
由于age没有索引,所以即使id、phone有索引,索引也会失效。所以需要针对age也要建立索引
- 数据分布影响
如果MySQL评估使用索引比全表更慢,则不会使用索引
1 | select * from tb_user where phone>='17799990005'; |
SQL提示
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的
use index(建议使用该索引)
1 | explain select * from tb_user use index(idx_user_pro) where profession = '软件工程'; |
ignore index(忽略使用此索引)
1 | explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程'; |
force index(强制使用该索引)
1 | explain select * from tb_user force index(idx_user_pro) where profession = '软件工程'; |
覆盖索引
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经能够全部找到),减少select *;
1 | explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0'; |
using index condition:查找使用了索引,但是需要回表查询数据
using where;using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
前缀索引
当字段类型为字符串(varchar、text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率
语法
1 | create index idx_xxx on table_name(column(n)); //n代表字符串的前n个字符 |
前缀长度
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的
1 | select count(distinct email)/ count(*) from tb_user; |
单列索引与联合索引
单列索引:即一个索引只包含单个列
联合索引:即一个索引包含了多个列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建立联合索引,而非单列索引
单列索引:
1 | explain select id,phone,name from tb_user where phone = '17799990010' and name = '韩信'; |
多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询
联合索引:
1 | create unique index idx_phone_name on tb_user(phone,name); |
索引设计原则
- 针对于数据量比较大,且查询比较频繁的表建立索引
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
- 如果是字符串类型的字段,字段的长度较长,可以针对字符串的特点,建立前缀索引
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率
- 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它,当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引更有效地用于查询
SQL优化
插入数据
insert优化
- 批量插入
1 | insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry'); |
- 手动事务提交
1 | start transaction; |
- 主键插入
1 | -- 主键乱序插入:8 1 9 21 80 2 4 15 89 5 7 3 |
大量批量插入数据
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令进行插入操作如下:
1 | # 客户端连接服务器时,还需要加上参数 --local-infile |
主键顺序插入性能高于乱序插入
主键优化
- 数据组织方式
在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table IOT)
- 页分裂
页可以为空,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果一行数据过大,就会行溢出),根据主键排列如果主键是乱序插入,会扰乱原来已排序好的页,MySQL 会通过新建页并调整原来已经存放在页中的数据来达到维护主键有序的效果,这便是页分裂。
- 页合并
当删除某一行记录时,实际上记录并没有被物理删除,只是记录被被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用,此之谓页合并。
MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或者索引的时候指定
主键设计原则
满足业务需求条件下,尽量降低主键长度
插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键
尽量不要使用UUID做主键或者其他自然主键,如身份证号
业务操作时,避免对主键的修改
order by 优化
Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都是FileSort排序Using index:通过有序索引顺序扫描直接返回有序结果,这种情况即为Using index,不需要额外排序,操作效率高
1 | # 没有创建索引时,根据age,phone进行排序 |
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
- 尽量使用覆盖索引
- 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
- 如果不可避免地出现了filesort,大数据量排序时,可以适当增大排序缓冲区sort_buffer_size(默认256k)
group by 优化
1 | # 删除当前的联合索引 |
- 在分组操作时,可以通过索引来提高效率
- 分组操作时,索引的使用也是满足最左前缀法则的
limit优化
limit 在大数据量的分页时越往后所需时间越长,因为 limit 底层默认走的是全表扫描。官方给出的优化方案是通过覆盖索引和子查询的方式提高 limit 的分页效率。
1 | # 先拿到id |
上述方案使用了多表查询对 limit 语句进行优化。实际上,如果 limit 语句使用了 where 子句,可以在 where 子句所涉及到的字段添加索引,可以有效提高查询效率。
count优化
1 | explain select count(*) from tb_user; |
- MySIAM引擎把一个表的总行数放到了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高(但是前提是没有 where 条件,即查询的是总数)
- InnoDB引擎在执行count(*)时,需要把数据一行一行地从引擎中读取出来,然后累积计数
优化思路:自己计数(可以借助于redis这样的数 据库进行,但是如果是带条件的count又比较麻烦了)。
count()是一个聚合函数,对于返回的结果集,一行行地判断,如果count函数的参数不是NULL,累计值就加1,否则不加,最后返回累计值
count的几种用法
| count用法 | 说明 |
|---|---|
| count(*) | InnoDB引擎并不会把全部字段取出来,而是做了专门的优化,不取值,服务层直接按行进行累加 |
| count(主键) | InnoDB引擎会遍历整张表,把每一行的主键id值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为NULL) |
| count(字段) | 没有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层后,服务层判断是否为null,不为null,计数累加;有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行累加 |
| count(1) | InnoDB会遍历整张表,但不取值。服务层对于返回的每一层,放一个数字”1“进去,直接按行累加 |
根据效率排序,count(字段) < count(主键id) < count(1) ≈ count(*)
update优化
1 | update student set no = '2000100100' where id = 1; -- 有inidex |
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁
视图
视图(view)是一种虚拟存在的表,视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的,通俗地讲,视图只保存了查询的SQL逻辑,不保存查询结果,所以我们在创建视图的时候,主要的任务就是创建这条SQL查询语句
基本语法
- 创建
1 | create [or replace] view 视图名称[(列名列表)] as select语句 [with[cascaded | local]check option] |
- 查询
1 | -- 查看创建视图语句 |
- 修改
1 | -- 方式一: |
- 删除
1 | drop view [if exists] 视图名称 [,视图名称]... |
演示示例:
1 | -- 创建视图 |
检查选项
当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插 入,更新,删除,以使其符合视图的定义。 MySQL允许基于另一个视图创建视图,它还会检查依赖视 图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项: CASCADED 和 LOCAL ,默认值为 CASCADED 。
1 | create or replace view stu_v_1 as select id,name from student where id <= 20 with cascaded check option; -- 检查选项 |
CASCADED(级联)
比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 cascaded,但是v1视图 创建时未指定检查选项。 则在执行检查时,不仅会检查v2,还会级联检查v2的关联视图v1。

LOCAL(本地)
比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 local ,但是v1视图创 建时未指定检查选项。 则在执行检查时,知会检查v2,不会检查v2的关联视图v1

更新
要使视图更新,视图中的行与基础表中的行之间必须存在一对一的关系,如果视图中包含以下任何一项,则视图不可更新:
- 聚合函数或窗口函数(SUM()、MIN()、MAX()、COUNT()等)
- DISTINCT
- GROUP BY
- HAVING
- UNION 或 UNION ALL
作用
- 简单
视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件
- 安全
数据库可以授权,但不能授权到数据库中特定行和特定的列上,通过视图用户只能查询和修改他们所能见到的数据
- 数据独立
视图可以帮助用户屏蔽真实表结构变化带来的影响
存储过程
存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用
特点:
- 封装、复用
- 可以接收参数,也可以返回数据
- 减少网络交互,效率提升
基本语法
- 创建
1 | create procedure 存储过程名称([参数列表]) |
- 调用
1 | call 名称 ([参数]); |
- 查看
1 | SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询指定数据库的存储过程及状态信息 |
- 删除
1 | DROP PROCEDURE [IF EXISTS] 存储过程名称; |
注意:在命令行中,执行创建存储过程的SQL时,需要通过关键字delimiter指定的SQL语句的结束符
1 | delimiter $$ -- 表示SQL语句将以$$符号表示结束符 |
变量
系统变量是MySQL服务器提供的,不是用户定义的,属于服务层面。分为全局变量(GLOBAL)、会话变量(SESSION)
- 查看系统变量
1 | show [session | global] variables; -- 查看所有系统变量 |
- 设置系统变量
1 | set [session | global] 系统变量名 = 值; |
注意:
- 如果没有指定SESSION / GLOBAL ,默认是SESSION,会话变量
- mysql服务重新启动后,所设置的全局参数会失效,要想不失效,可以在/etc/my.cnf中配置
用户自定义变量 是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用@变量名使用就可以,其作用域为当前连接
- 赋值
1 | set @var_name = expr[,@var_name = expr]...; |
1 | select @var_name := expr[,@var_name := expr]...; |
- 使用
1 | select @var_name; |
注意:用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL
局部变量 是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明,可用于存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN…END块
- 声明
1 | declare 变量名 变量类型[default...]; |
变量的类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等
- 赋值
1 | set 变量名 = 值; |
if判断
语法
1 | IF 条件1 THEN |
参数
| 类型 | 含义 | 备注 |
|---|---|---|
| IN | 该类参数作为输入,也就是需要调用时传入值 | 默认 |
| OUT | 该类参数作为输出,也就是该参数可以作为返回值 | |
| INOUT | 既可以作为输入参数,也可以作为输出参数 |
用法
1 | create procedure 存储过程名称 ([IN / OUT / INOUT 参数名 参数类型]) |
case
- 语法一
1 | case case_value |
- 语法二
1 | case |
循环
while
while循环是有条件的循环控制语句,满足条件后,再执行循环体中的SQL语句,具体语法为:
1 | # 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑‘ |
repeat
repeat是有条件的循环控制语句,当满足条件时退出循环,具体语法为:
1 | # 先执行一次循环,任何判断逻辑是否满足,如果满足,则退出,如果不满足,则继续下一次循环 |
loop
LOOP实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用来实现简单的死循环,LOOP可以配合以下两个语句使用:
- LEAVE:配合循环使用,退出循环
- ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环
1 | [begin_label:] loop |
1 | leave label; -- 退出指定标记的循环体 |
游标
游标(CURSOR)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下:
- 声明游标
1 | declare 游标名称 cursor for 查询语句; |
- 打开游标
1 | open 游标名称; |
- 获取游标记录
1 | fetch 游标名称 into 变量[, 变量]; |
- 关闭游标
1 | close 游标名称; |
条件处理程序(Handler):可以用来定义在流程控制结构执行过程中遇到的问题时相应的处理步骤,具体语法如下:
1 | DECLARE handler_action HANDLER FOR condition_value [, condition_value]... statement ; |
存储函数
存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的,具体语法如下:
1 | CREATE FUNCTION 存储函数名称 ([ 参数列表 ]) |
案例:
1 | -- 存储函数 |
在mysql8.0版本中binlog默认是开启的,一旦开启了,mysql就要求在定义存储过程时,需要指定 characteristic特性,否则就会报如下错误:

触发器
触发器是与表有关的数据库对象,指在insert / update / delete之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录、数据校验等操作
使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是类似的,现在触发器还只支持行级触发,不支持语句级触发
| 触发器类型 | NEW和OLD |
|---|---|
| INSERT型触发器 | NEW表示将要或者已经新增的数据 |
| UPDATE型触发器 | OLD表示修改之前的数据,NEW表示将要或者已经修改之后的数据 |
| DELETE型触发器 | OLD表示将要或者已经删除的数据 |
语法
- 创建
1 | create trigger trigger_name |
- 查看
1 | show triggers; |
- 删除
1 | drop trigger [schema_name.] trigger_name; -- 如果没有指定schema_name,默认为当前数据库 |
insert触发器
1 | - insert触发器 |
update触发器
1 | -- 修改数据触发器 |
delete触发器
1 | -- 删除数据触发器 |
锁
锁是计算机协调多个进程或者线程并发访问某一资源的机制,在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种提供许多用户共享的资源,如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问虚性能的一个重要因素,从这个角度上来讲,锁对数据库而言显得尤其重要,也更加复杂。
MySQL中的锁,按照锁的粒度分为以下三类:
- 全局锁:锁定数据库中的所有表
- 表级锁:每次操作锁定整张表
- 行级锁:每次操作锁住对应的行数据
全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞,其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性

语法
- 加全局锁
1 | flush tables with read lock ; |
- 数据备份
1 | mysqldump -uroot –p1234 itcast > itcast.sql |
- 释放锁
1 | unlock tables ; |
特点
数据库中加全局锁,是一个比较重的操作,存在以下问题:
- 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆
- 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟
在InnoDB引擎中,我们可以在备份时加入参数 –single-transaction 参数来完成不加锁的一致性数据备份
1 | mysqldump --single-transaction -uroot –p1234 itcast > itcast.sql |
表级锁
表级锁,每次操作锁定整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中
对于表级锁,主要分为以下三类:
- 表锁
- 元数据锁(meta data lock,MDL)
- 意向锁
表锁
对于表锁,分为以下两类:
- 表共享读锁(read lock)
- 表独占写锁(write lock)
语法
- 加锁:lock tables 表名… read / write
- 释放锁:unlock tables / 客户端断开连接
读锁

写锁

结论: 读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。
元数据锁(meta data lock,MDL)
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上,MDL锁主要作用是维护表元数据的数据一致性,在表上有活动业务时,不可以对元数据进行写入操作。为了避免DML与DDL产生冲突,保证对写的正确性
在MySQL5.5中引入了MDL,当对一张表进行增删改查时,加MDL读锁(共享),当对表结构进行变更操作的时候,加MDL写锁(排他)。
| 对应SQL | 锁类型 | 说明 |
|---|---|---|
| lock tables xxx read / write | SHARED_READ_ONLY / SHARED_NO_READ_WRITE | |
| select、select … lock in share mode | SHARED_READ(本会话只能读不能写,其他会话可以读也可以写) | 与SHARED_READ、 SHARED_WRITE兼容,与 EXCLUSIVE互斥 |
| insert、update、delete、select … for update | SHARED_WRITE(所有会话都可以读,但只有本会话可以写) | 与SHARED_READ、 SHARED_WRITE兼容,与 EXCLUSIVE互斥 |
| alter table … | EXCLUSIVE(本会话可以读也可以写,但是别的会话不能读也不能写) | 与其他的MDL都互斥 |
查看元数据锁
1 | select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ; |
意向锁
注意到一个场景:当我们利用update语句对表的数据进行操作时,这个时候系统会自动给表加上一个元数据锁,使得所有会话都可以读,但是只有本会话可以写。假设这个时候有另外一个客户端对这个表执行lock tables xxx read / write操作,欲给这个表上一个表锁,这时会发生锁的冲突。为了避免锁的冲突,客户端需要一行一行检查表的行数据以确定是否有元数据锁,这会使得性能下降。
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行 数据是否加锁,使用意向锁来减少表锁的检查。
意向锁分有:
- 意向共享锁(IS):由语句select … lock in share mode 添加,与表锁读锁(read)兼容,与表锁写锁(write)互斥。
- 意向排他锁(IX):由insert、update、delete、select … for update 添加,与表锁读锁和写锁都互斥,意向锁之间不会互斥。
可以通过以下SQL语句来查看意向锁及行锁的加锁情况:
1 | select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from |
注意:一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
行级锁
行级锁,每次操作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最低,并发度最高,应用在InnoDB存储引擎中
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录的锁,对于行级锁,主要分为以下三大类:
- 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对行进行update和delete。在RC、RR隔离级别下都支持
- 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持
- 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap,在RR隔离级别下支持
行锁
InnoDB实现了以下两种类型的行锁:
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同的数据集的排他锁
- 排他锁(X):允许获取其他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁
| 当前锁类型(下)/ 请求锁类型(右) | S(共享锁) | X(排他锁) |
|---|---|---|
| S(共享锁) | 兼容 | 冲突 |
| X(排他锁) | 冲突 | 冲突 |
| SQL | 行锁类型 | 说明 |
|---|---|---|
| INSERT … | 排他锁 | 自动加锁 |
| UPDATE … | 排他锁 | 自动加锁 |
| SELECT(正常) | 不加任何锁 | |
| SELECT … LOCK IN SHARE MODE | 共享锁 | 需要手动在SELECT之后加LOCK SHARE MODE |
| SELECT … FOR UPDATE | 拍他锁 | 需要手动在SELECT之后加FOR UPDATE |
| DELETE … | 排他锁 | 自动加锁 |
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜 索和索引扫描,以防止幻读。
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
- InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
1 | select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from |
间隙锁&临键锁
默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读
- 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁
- 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁
- 索引上的范围查询(唯一索引)– 会访问到不满足条件的第一个值为止
注意:间隙锁唯一目的是防止其他事务插入间隙,间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一个间隙上采用间隙锁
InnoDB引擎
逻辑存储结构
InnoDB的逻辑存储结构如下图所示:

- 表空间, 表空间是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架构图,左侧为内存结构,右侧为磁盘结构。

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';
Change Buffer:更改缓冲区(针对非唯一二级索引页),在执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区 Change Buffer 中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中。
Change Buffer 存在的意义:
与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新 可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了 ChangeBuffer之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。
Adaptive Hash Index:自适应hash索引,用于优化对Buffer Pool 数据的查询。InnoDB存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。
自适应哈希索引,无需人工干预,是系统根据情况自动完成的。
参数:adpative_hash_index
Log Buffer:日志缓冲区,用来保存要写入到磁盘中的log日志数据(red log、undo log),默认大小为16MB,日志缓冲区的日志会定期刷新到磁盘中,如果有需要更新、插入或者删除许多行的事务,增加日志缓冲区的大小可以节省磁盘I / O。
参数:
innodb_log_buffer_size:缓冲区大小
innodb_flush_log_trx_commit:日志刷新到磁盘时机
- 1:日志在每次事务提交时写入并刷新到磁盘中
- 0:每秒将日志写入并刷新到磁盘一次
- 2:日志在每次事务提交之后写入,并每秒刷新到磁盘一次
磁盘结构
System Tablespace:系统表空间是更改缓冲区的存储区域,如果表是在系统表空间而不是每个表文件或者通用表空间中创建的,它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等)
参数:innnodb_data_file_path
File-Per-Table Tablespaces:每个表的文件表空间包含单个InnoDB表的数据和索引,并存储在文件系统上的单个数据文件上
参数:innodb_file_per_table
General Tablespaces:通用表空间,需要通过CREATE TABLESPACE语法创建通用表空间,在创建表时,可以指定该表空间
- 创建表空间
1 | CREATE TABLESPACE ts_name ADD DATAFILE 'file_name' ENGINE = engine_name; |
- 创建表时指定表空间
1 | CREATE TABLE xxx ... TABLESPACE ts_name; |
Updo Tablespaces:撤销表空间,MySQL实例在初始化会自动创建两个默认的undo表空间(初始大小16M),用于存储undo log 日志
Temporary Tablespaces:InnoDB使用会话临时表空间和全局临时表空间,存储用户创建的临时表等数据
Doublewrite Buffer Files:双写缓冲区,innoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件 中,便于系统异常时恢复数据。

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

后台线程

在InnoDB的后台线程中,分为4类,分别是:Master Thread 、IO Thread、Purge Thread、 Page Cleaner Thread。
Master Thread
核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓冲、undo页的回收
IO Thread
在InnoDB存储引擎中大量使用了AIO请求,这样可以极大地提高数据库的性能,而IO Thread 主要负责这些IO请求的回调
| 线程类型 | 默认个数 | 职责 |
|---|---|---|
| Read thread | 4 | 负责读操作 |
| Write thread | 4 | 负责写操作 |
| Log thread | 1 | 负责将日志缓冲区刷新到磁盘 |
| Insert buffer thread | 1 | 负责将写缓冲区内容刷新到磁盘 |
我们可以通过以下的这条指令,查看到InnoDB的状态信息,其中就包含IO Thread信息。
1 | show engine innodb status ; |
Purge Thread
主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收。
Page Cleaner Thread
协助Master Thread 刷新脏页到磁盘的线程,它可以减轻Masrter Thread 的工作压力,减少阻塞
事务原理
事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或者撤销操作请求,即这些操作要么同时成功,要么同时失败。
事务四大特性(ACID)
原子性(Atomicty):事务是不可分割的最小操作单元,要么全部成功,要么全部失败
一致性(Consistency):事务完成时,必须使所有数据都保持一致状态
隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响下的独立环境下运行
持久性(Durability):事务一旦提交或者回滚,它对数据库中的数据的改变就是永久的
而对于这四大特性,实际上分为两个部分。 其中的原子性、一致性、持久化,实际上是由InnoDB中的两份日志来保证的,一份是redo log日志,一份是undo log日志。 而持久性是通过数据库的锁, 加上MVCC来保证的。
red log
重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性
该日志文件由两部分组成:重做缓冲日志(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者是在磁盘中。当事务提交之后会把所有修改信息都存到该日志中,再用于刷新脏页到磁盘,发生错误时,进行数据恢复使用
在InnoDB引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数据页。 当我们在一个事务中,执行多个增删改的操作时,InnoDB引擎会先操作缓冲池中的数据,如果缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中的数据修改,修改后的数据页我们称为脏页。 而脏页则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性。

有了redo log之后,当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在redo log buffer中。在事务提交时,会将redo log buffer中的数据刷新到redo log磁盘文件中。 过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于redo log进行数据 恢复,这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘 或 或者涉及到的数据已经落盘,此时redo log就没有作用了,就可以删除了,所以存在的两个redo log文件是循环写的。
在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而redo log在 往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。 这种先写日志的方式,称之为 WAL(Write-Ahead Logging)。
undo log
回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和 MVCC(多版本并发控制) 。
undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的 update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些 日志可能还用于MVCC。
Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含1024个undo log segment。
MVCC
- 当前读
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁,对于我们日常的操作,如:select … lock in share mode (共享锁),select … for update、update、insert、delete(排他锁)都是一种当前读。
- 快照读
简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读
- Read Committed:每次select,都生成一个快照读
- Repeatable Read:开始事务后第一个select语句才是快照读的地方
- Serializable:快照读会退化为当前读
MVCC,全称Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。
实现原理
- 记录当中的隐藏字段
| 隐藏字段 | 含义 |
|---|---|
| DB_TRX_ID | 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID |
| DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本 |
| DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段 |
idb2sdi 表空间文件名:查看表空间文件内容
1 | ibd2sdi 表名.ibd |
- undo log
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志,当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除,而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会被立即删除
- undo log版本链

不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
- readview
ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护当前活跃的事务(未提交的)id。
ReadView包含了四个核心字段:
| 字段 | 含义 |
|---|---|
| m_ids | 当前活跃的事务id集合 |
| min_trx_id | 最小活跃事务id |
| max_trx_id | 预分配事务id,当前最大事务id1(因为事务id是自增的) |
| creator_trx_id | ReadView创建者的事务id |
版本链数据访问规则
trx_id:代表的是当前事务ID。
trx_id == creator_trx_id 可以访问当前版本 → 说明数据是当前这个事务更改的
trx_id < min_trx_id 可以访问当前版本 → 说明数据已经提交了
trx_id > max_trx_id 不可以访问该版本 → 说明当前事务是在ReadView生成之后才开始
min_trx_id <= trx_id <= max_trx_id 如果trx_id不在m_ids中是可以访问该版本的 → 说明数据已经提交了
不同的隔离级别,生成ReadView的时机不同:
- READ COMMITTED:在事务中每一次执行快照读时生成ReadView
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView
RC隔离级别下,在事务中的每一次执行快照读时生成ReadView

RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView
