MYSQL 学习笔记
1. 数据库概述
1.1 为什么使用数据库
持久化:将内存中的数据保存在关系型或者非关系型数据库中,方便下一次使用。
1.2 数据库的相关概念
DB:数据库(Database)
即存储数据的“仓库”,其本质是一个文件系统。它保存了一系列有组织的数据。
DBMS:数据库管理系统(Database Management System)
是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控制。用户通过数据库管理系统访问数据库中表内的数据。
SQL:结构化查询语言(Structured Query Language)
专门用来与数据库通信的语言。
1.3 常见的数据库管理系统排名(DBMS)
查看数据库最新排名:https://db-engines.com/en/ranking
对应的走势图:https://db-engines.com/en/ranking_trend
1.4 常见的数据库介绍
Oracle
1979 年,Oracle 2 诞生,它是第一个商用的 RDBMS(关系型数据库管理系统)。随着 Oracle 软件的名气越来越大,公司也改名叫 Oracle 公司。
2007年,总计85亿美金收购 BEA Systems。
2009年,总计74亿美金收购SUN。此前的 2008 年,SUN 以10亿美金收购 MySQL。意味着Oracle 同时拥有了 MySQL 的管理权,至此 Oracle 在数据库领域中成为绝对的领导者。
2013年,甲骨文超越 IBM,成为继 Microsoft 后全球第二大软件公司。
如今 Oracle 的年收入达到了 400 亿美金,足以证明商用(收费)数据库软件的价值。
SQL Server
SQL Server 是微软开发的大型商业数据库,诞生于 1989 年。C#、.net 等语言常使用,与WinNT 完全集成,也可以很好地与 Microsoft BackOffice 产品集成。
DB2
IBM 公司的数据库产品,收费的。常应用在银行系统中。
PostgreSQL
PostgreSQL 的稳定性极强,最符合SQL标准,开放源码,具备商业级 DBMS 质量。PG 对数据量大的文本以及 SQL 处理较快。
SyBase
已经淡出历史舞台。提供了一个非常专业数据建模的工具 PowerDesigner。
SQLite
嵌入式的小型数据库,应用在手机端。 零配置,SQlite3 不用安装,不用配置,不用启动,关闭或者配置数据库实例。当系统崩溃后不用做任何恢复操作,再下次使用数据库的时候自动恢复。
informix
IBM 公司出品,取自 Information 和 Unix 的结合,它是第一个被移植到 Linux 上的商业数据库产品。仅运行于 unix/linux 平台,命令行操作。 性能较高,支持集群,适应于安全性要求极高的系统,尤其是银行,证券系统的应用。
1.5 MySQL介绍
- MySQL 是一个
开放源代码的关系型数据库管理系统
,由瑞典 MySQL AB(创始人Michael Widenius)公司 1995 年开发,迅速成为开源数据库的 No.1。 - 2008被
Sun
收购(10亿美金),2009年 Sun 被Oracle
收购。MariaDB
应运而生。(MySQL 的创造者担心 MySQL 有闭源的风险,因此创建了 MySQL 的分支项目 MariaDB) - MySQL5.x 版本之后分为
社区版
和商业版
。 - MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
- MySQL是开源的,所以你不需要支付额外的费用。
- MySQL是可以定制的,采用了
GPL(GNU General Public License)
协议,你可以修改源码来开发自己的MySQL系统。 - MySQL支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
- MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持
4GB
,64位系统支持最大的表文件为8TB
。 - MySQL使用
标准的SQL数据语言
形式。 - MySQL可以允许运行于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP 和 Ruby 等。
1.5.1 MYSQL 发展史
MySQL的历史就是整个互联网的发展史。互联网业务从社交领域、电商领域到金融领域的发展,推动着应用对数据库的需求提升,对传统的数据库服务能力提出了挑战。高并发、高性能、高可用、轻资源、易维护、易扩展的需求,促进了MySQL的长足发展。
1.5.2 关于 MYSQL 8.0
MySQL从5.7版本直接跳跃发布了8.0版本
,可见这是一个令人兴奋的里程碑版本。MySQL 8版本在功能上做了显著的改进与增强,开发者对MySQL的源代码进行了重构,最突出的一点是对MySQL Optimizer优化器进行了改进。不仅在速度上得到了改善,还为用户带来了更好的性能和更棒的体验。
1.5.3 为什么选择 MYSQL
为什么如此多的厂商要选用 MySQL?大概总结的原因主要有以下几点:
- 开放源代码,使用成本低。
- 性能卓越,服务稳定。
- 软件体积小,使用简单,并且易于维护。
- 历史悠久,社区用户非常活跃,遇到问题可以寻求帮助。
- 许多互联网公司在用,经过了时间的验证。
1.5.4 Oracle vs MySQL
Oracle 更适合大型跨国企业的使用,因为他们对费用不敏感,但是对性能要求以及安全性有更高的要求。
MySQL 由于其体积小、速度快、总体拥有成本低,可处理上千万条记录的大型数据库,尤其是开放源码这一特点,使得很多互联网公司、中小型网站选择了MySQL作为网站数据库(Facebook,Twitter,YouTube,阿里巴巴/蚂蚁金服,去哪儿,美团外卖,腾讯)。
1.6 关系型数据库与非关系型数据库
1.6.1 关系型数据库(RDBMS)
- 关系型数据库以
行(row)
和列(column)
的形式存储数据,以便于用户理解。这一系列的行和列被称为表(table)
,一组表组成了一个库(database); - 表与表之间的数据记录有关系(relationship)。现实世界中的各种实体以及实体之间的各种联系均用
关系模型
来表示。关系型数据库,就是建立在关系模型
基础上的数据库; - 优势:
- 复杂查询:可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询;
- 事务支持:使得对于安全性能很高的数据访问要求得以实现。
1.6.2 非关系型数据库(非RDBMS)
- 非关系型数据库,可看成传统关系型数据库的功能
阉割版本
,基于键值对存储数据,不需要经过SQL层的解析,性能非常高
。同时,通过减少不常用的功能,进一步提高性能。 - 大部分主流的非关系型数据库都是免费的。
- NoSQL 泛指非关系型数据库,包括了榜单上的键值型数据库、文档型数据库、搜索引擎和列存储等,除此以外还包括图形数据库。也只有用 NoSQL 一词才能将这些技术囊括进来。
1.6.3 常见非关系型数据库
键值型数据库
键值型数据库通过 Key-Value 键值的方式来存储数据,其中 Key 和 Value 可以是简单的对象,也可以是复杂的对象。Key 作为唯一的标识符,优点是查找速度快,在这方面明显优于关系型数据库,缺点是无法像关系型数据库一样使用条件过滤(比如 WHERE),如果你不知道去哪里找数据,就要遍历所有的键,这就会消耗大量的计算。
键值型数据库典型的使用场景是作为
内存缓存
。Redis
是最流行的键值型数据库。搜索引擎数据库
虽然关系型数据库采用了索引提升检索效率,但是针对全文索引效率却较低。搜索引擎数据库是应用在搜索引擎领域的数据存储形式,由于搜索引擎会爬取大量的数据,并以特定的格式进行存储,这样在检索的时候才能保证性能最优。核心原理是“倒排索引”。
典型产品:Solr、Elasticsearch、Splunk 等。
列式数据库
列式数据库是相对于行式存储的数据库,Oracle、MySQL、SQL Server 等数据库都是采用的行式存储(Row-based),而列式数据库是将数据按照列存储到数据库中,这样做的好处是可以大量降低系统的 I/O,适合于分布式文件系统,不足在于功能相对有限。典型产品:HBase等。
图形数据库
图形数据库,利用了图这种数据结构存储了实体(对象)之间的关系。图形数据库最典型的例子就是社交网络中人与人的关系,数据模型主要是以节点和边(关系)来实现,特点在于能高效地解决复杂的关系问题。
图形数据库顾名思义,就是一种存储图形关系的数据库。它利用了图这种数据结构存储了实体(对象)之间的关系。关系型数据用于存储明确关系的数据,但对于复杂关系的数据存储却有些力不从心。如社交网络中人物之间的关系,如果用关系型数据库则非常复杂,用图形数据库将非常简单。典型产品:Neo4J、InfoGrid等。
1.6.4 NoSQL 的演变
由于 SQL 一直称霸 DBMS,因此许多人在思考是否有一种数据库技术能远离 SQL,于是 NoSQL 诞生了,但是随着发展却发现越来越离不开 SQL。到目前为止 NoSQL 阵营中的 DBMS 都会有实现类似 SQL 的功能。下面是“NoSQL”这个名词在不同时期的诠释,从这些释义的变化中可以看出 NoSQL 功能的演变
:
1970:NoSQL = We have no SQL
1980:NoSQL = Know SQL
2000:NoSQL = No SQL!
2005:NoSQL = Not only SQL
2013:NoSQL = No, SQL!
NoSQL 对 SQL 做出了很好的补充,比如实际开发中,有很多业务需求,其实并不需要完整的关系型数据库功能,非关系型数据库的功能就足够使用了。这种情况下,使用性能更高
、成本更低
的非关系型数据库当然是更明智的选择。比如:日志收集、排行榜、定时器等。
1.7 关系型数据库设计规则
- 关系型数据库的典型数据结构就是
数据表
,这些数据表的组成都是结构化的(Structured)。 - 将数据放到表中,表再放到库中。
- 一个数据库中可以有多个表,每个表都有一个名字,用来标识自己。表名具有唯一性。
- 表具有一些特性,这些特性定义了数据在表中如何存储,类似 Java 和 Python 中 “类”的设计。
1.7.1 表、记录、字段
- E-R(entity-relationship,实体-联系)模型中有三个主要概念是:
实体集
、属性
、联系集
。 - 一个实体集(class)对应于数据库中的一个表(table),一个实体(instance)则对应于数据库表中的一行(row),也称为一条记录(record)。一个属性(attribute)对应于数据库表中的一列(column),也称为一个字段(field)。
ORM思想 (Object Relational Mapping)体现:
数据库中的一个表 <—-> Java或Python中的一个类
表中的一条数据 <—-> 类中的一个对象(或实体)
表中的一个列 <——> 类中的一个字段、属性(field)
1.7.2 表的关联关系
- 表与表之间的数据记录有关系(relationship)。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。
- 四种:一对一关联、一对多关联、多对多关联、自我引用。
一对一关联(one-to-one)
- 在实际的开发中应用不多,因为一对一可以创建成一张表。
- 举例:设计
学生表
:学号、姓名、手机号码、班级、系别、身份证号码、家庭住址、籍贯、紧急联系人、…- 拆为两个表:两个表的记录是一一对应关系。
基础信息表
(常用信息):学号、姓名、手机号码、班级、系别档案信息表
(不常用信息):学号、身份证号码、家庭住址、籍贯、紧急联系人、…
- 两种建表原则:
- 外键唯一:主表的主键和从表的外键(唯一),形成主外键关系,外键唯一。
- 外键是主键:主表的主键和从表的主键,形成主外键关系。
一对多关系(one-to-many)
- 常见实例场景:
客户表和订单表
,分类表和商品表
,部门表和员工表
。 - 举例:
- 员工表:编号、姓名、…、所属部门
- 部门表:编号、名称、简介
- 一对多建表原则:在从表(多方)创建一个字段,字段作为外键指向主表(一方)的主键。
多对多(many-to-many)
要表示多对多关系,必须创建第三个表,该表通常称为联接表
,它将多对多关系划分为两个一对多关系。将这两个表的主键都插入到第三个表中。
自我引用(Self reference)
举例:评论,树形分类
2. 环境搭建
使用远程的 docker 安装 mysql
拉取镜像;
1
docker pull mysql
启动 mysql;
1
2
3
4
5
6docker run -d --restart=always --name="mysql" -p 3306:3306 --privileged=true \
-v /usr/local/mysql/log:/var/log/mysql \
-v /usr/local/mysql/data:/var/lib/mysql \
-v /usr/local/mysql/conf:/etc/mysql/conf.d \
-e MYSQL_ROOT_PASSWORD=12345678 \
mysql:latest在
/usr/local/mysql/conf
下新建my.cnf
,写入以下内容;1
2
3
4
5
6
7
8
9[client]
default_character_set=utf8mb4
[mysqld]
collation_server=utf8mb4_general_ci
character_set_server=utf8mb4
skip-name-resolve
max_connections=500
wait_timeout=10
interactive_timeout=10进入 mysql 容器内,执行以下语句,创建远程登录的用户;
1
2
3
4CREATE USER 'root'@'%' IDENTIFIED BY 'your_password'; # 创建用户
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; # 赋予权限
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'your_password'; # 修改密码
FLUSH PRIVILEGES; # 刷新权限重启 mysql 容器。
3. 基础篇
3.1 SQL分类
SQL语言在功能上主要分为如下3大类:
DDL(Data Definition Languages、数据定义语言),这些语句定义了不同的数据库、表、视图、索引等数据库对象,还可以用来创建、删除、修改数据库和数据表的结构。
- 主要的语句关键字包括
CREATE
、DROP
、ALTER
等。
- 主要的语句关键字包括
DML(Data Manipulation Language、数据操作语言),用于添加、删除、更新和查询数据库记录,并检查数据完整性。
- 主要的语句关键字包括
INSERT
、DELETE
、UPDATE
、SELECT
等。 - SELECT是SQL语言的基础,最为重要。
- 主要的语句关键字包括
DCL(Data Control Language、数据控制语言),用于定义数据库、表、字段、用户的访问权限和安全级别。
- 主要的语句关键字包括
GRANT
、REVOKE
、COMMIT
、ROLLBACK
、SAVEPOINT
等。
- 主要的语句关键字包括
因为查询语句使用的非常的频繁,所以很多人把查询语句单拎出来一类:DQL(数据查询语言)。
还有单独将
COMMIT
、ROLLBACK
取出来称为TCL (Transaction Control Language,事务控制语言)。小知识:导入数据库文件命令
1 >mysql> source d:\mysqldb.sql
3.2 DDL(数据定义语言)
3.2.1 数据库相关
查询所有数据库:
1
SHOW DATABASES;
查询当前数据库:
1
SELECT DATABASE();
创建数据库:
1
CREATE DATABASE [ IF NOT EXISTS ] 数据库名 [ DEFAULT CHARSET 字符集] [COLLATE 排序规则 ];
删除数据库:
1
DROP DATABASE [ IF EXISTS ] 数据库名;
使用数据库:
1
USE 数据库名;
3.2.2 表相关
查询当前数据库所有表:
1
SHOW TABLES;
查询表结构:
1
DESC 表名;
查询指定表的建表语句:
1
SHOW CREATE TABLE 表名;
创建表:
1
2
3
4
5
6
7CREATE TABLE 表名(
字段1 类型 [COMMENT 字段1注释],
字段2 类型 [COMMENT 字段2注释],
字段3 类型 [COMMENT 字段3注释],
...
字段n 字段n类型 [COMMENT 字段n注释]
)[ COMMENT 表注释 ];添加字段:
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 表名
3.3 DML(数据操作语言)
添加数据:
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 条件 ];
3.4 DQL(数据查询语言)
语法:
下面的数字代表执行顺序
1 | SELECT 5 |
3.3.1 基础查询
查询多个字段:
1
SELECT 字段1, 字段2, 字段3, ... FROM 表名;
1
SELECT * FROM 表名;
设置别名:
1
SELECT 字段1 [ AS 别名1 ], 字段2 [ AS 别名2 ], 字段3 [ AS 别名3 ], ... FROM 表名;
1
SELECT 字段1 [ 别名1 ], 字段2 [ 别名2 ], 字段3 [ 别名3 ], ... FROM 表名;
去除重复记录:
1
SELECT DISTINCT 字段列表 FROM 表名;
转义:
1
SELECT * FROM 表名 WHERE name LIKE '/_张三' ESCAPE '/'
条件查询:
1
SELECT 字段列表 FROM 表名 WHERE 条件列表;
3.3.2 条件查询
比较运算符 | 功能 |
---|---|
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
= | 等于 |
<> 或 != | 不等于 |
BETWEEN … AND … | 在某个范围内(含最小、最大值) |
IN(…) | 在in之后的列表中的值,多选一 |
LIKE 占位符 | 模糊匹配(_匹配单个字符,%匹配任意个字符) |
IS NULL | 是NULL |
逻辑运算符 | 功能 |
---|---|
AND 或 && | 并且(多个条件同时成立) |
OR 或 || | 或者(多个条件任意一个成立) |
NOT 或 ! | 非,不是 |
例子:
1 | -- 年龄等于30 |
3.3.3 聚合查询
常见聚合函数:
函数 | 功能 |
---|---|
count | 统计数量 |
max | 最大值 |
min | 最小值 |
avg | 平均值 |
sum | 求和 |
语法:
1 | SELECT 聚合函数(字段列表) FROM 表名; |
例:
1 | SELECT count(id) from employee where workaddress = "广东省"; |
3.3.4 分组查询
语法:
1 | SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后的过滤条件 ]; |
where 和 having 的区别:
- 执行时机不同:where是分组之前进行过滤,不满足where条件不参与分组;having是分组后对结果进行过滤。
- 判断条件不同:where不能对聚合函数进行判断,而having可以。
例:
1 | -- 根据性别分组,统计男性和女性数量(只显示分组数量,不显示哪个是男哪个是女) |
注意事项
- 执行顺序:where > 聚合函数 > having
- 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义
3.3.5 排序
语法:
1 | SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1, 字段2 排序方式2; |
排序方式:
- ASC: 升序(默认)
- DESC: 降序
例子:
1 | -- 根据年龄升序排序 |
如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序
3.3.6 分页
语法:
1 | SELECT 字段列表 FROM 表名 LIMIT 起始索引, 每页记录数; |
例:
1 | -- 查询第一页数据,展示10条 |
注意事项
- 起始索引从0开始,起始索引 = (查询页码 - 1) * 每页显示记录数
- 分页查询是数据库的方言,不同数据库有不同实现,MySQL是LIMIT
- 如果查询的是第一页数据,起始索引可以省略,直接简写 LIMIT 10
3.5 DCL(数据控制语言)
3.5.1 管理用户
查询用户:
1
select * from mysql.user
创建用户:
1
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';
修改用户密码:
1
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码';
删除用户:
1
DROP USER '用户名'@'主机名';
主机名可以使用 % 通配
3.5.2 权限控制
常用权限:
权限 | 说明 |
---|---|
ALL, ALL PRIVILEGES | 所有权限 |
SELECT | 查询数据 |
INSERT | 插入数据 |
UPDATE | 修改数据 |
DELETE | 删除数据 |
ALTER | 修改表 |
DROP | 删除数据库/表/视图 |
CREATE | 创建数据库/表 |
查询权限:
1
SHOW GRANTS FOR '用户名'@'主机名';
授与权限:
1
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';
撤销权限:
1
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
注意事项
- 多个权限用逗号分隔
- 授权时,数据库名和表名可以用 * 进行通配,代表所有
3.6 函数
- 字符串函数
- 数值函数
- 日期函数
- 流程函数
3.6.1 字符串函数
常用函数:
函数 | 功能 |
---|---|
CONCAT(s1, s2, …, sn) | 字符串拼接,将s1, s2, …, sn拼接成一个字符串 |
LOWER(str) | 将字符串全部转为小写 |
UPPER(str) | 将字符串全部转为大写 |
LPAD(str, n, pad) | 左填充,用字符串pad对str的左边进行填充,达到n个字符串长度 |
RPAD(str, n, pad) | 右填充,用字符串pad对str的右边进行填充,达到n个字符串长度 |
TRIM(str) | 去掉字符串头部和尾部的空格 |
SUBSTRING(str, start, len) | 返回从字符串str从start位置起的len个长度的字符串 |
REPLACE(column, source, replace) | 替换字符串 |
使用示例:
1 | -- 拼接 |
3.6.2 数值函数
常见函数:
函数 | 功能 |
---|---|
CEIL(x) | 向上取整 |
FLOOR(x) | 向下取整 |
MOD(x, y) | 返回x/y的模 |
RAND() | 返回0~1内的随机数 |
ROUND(x, y) | 求参数x的四舍五入值,保留y位小数 |
3.6.3 日期函数
常用函数:
函数 | 功能 |
---|---|
CURDATE() | 返回当前日期 |
CURTIME() | 返回当前时间 |
NOW() | 返回当前日期和时间 |
YEAR(date) | 获取指定date的年份 |
MONTH(date) | 获取指定date的月份 |
DAY(date) | 获取指定date的日期 |
DATE_ADD(date, INTERVAL expr type) | 返回一个日期/时间值加上一个时间间隔expr后的时间值 |
DATEDIFF(date1, date2) | 返回起始时间date1和结束时间date2之间的天数 |
例子:
1 | -- DATE_ADD |
3.6.4 流程函数
常用函数:
函数 | 功能 |
---|---|
IF(value, t, f) | 如果value为true,则返回t,否则返回f |
IFNULL(value1, value2) | 如果value1不为空,返回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默认值 |
例:
1 | select |
3.7 约束
分类:
约束 | 描述 | 关键字 |
---|---|---|
非空约束 | 限制该字段的数据不能为null | NOT NULL |
唯一约束 | 保证该字段的所有数据都是唯一、不重复的 | UNIQUE |
主键约束 | 主键是一行数据的唯一标识,要求非空且唯一 | PRIMARY KEY |
默认约束 | 保存数据时,如果未指定该字段的值,则采用默认值 | DEFAULT |
检查约束(8.0.1版本后) | 保证字段值满足某一个条件 | CHECK |
外键约束 | 用来让两张图的数据之间建立连接,保证数据的一致性和完整性 | FOREIGN KEY |
自动增长:AUTO_INCREMENT
例:
1 | create table user( |
外键约束
不常用
格式:
创建表时添加
1
2
3
4
5CREATE TABLE 表名(
字段名 字段类型,
...
[CONSTRAINT] [外键名称] FOREIGN KEY(外键字段名) REFERENCES 主表(主表列名)
);创建表后添加
1
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表(主表列名);
删除外键
1
ALTER TABLE 表名 DROP FOREIGN KEY 外键名;
删除/更新行为
行为 | 说明 |
---|---|
NO ACTION | 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新(与RESTRICT一致) |
RESTRICT | 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新(与NO ACTION一致) |
CASCADE | 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则也删除/更新外键在子表中的记录 |
SET NULL | 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则设置子表中该外键值为null(要求该外键允许为null) |
SET DEFAULT | 父表有变更时,子表将外键设为一个默认值(Innodb不支持) |
更改删除/更新行为:
1 | ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段) REFERENCES 主表名(主表字段名) ON UPDATE 行为 ON DELETE 行为; |
3.8 多表查询
3.8.1 多表关系
- 一对多(多对一)
- 多对多
- 一对一
一对多
案例:部门与员工
关系:一个部门对应多个员工,一个员工对应一个部门
实现:在多的一方建立外键,指向一的一方的主键
多对多
案例:学生与课程
关系:一个学生可以选多门课程,一门课程也可以供多个学生选修
实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
一对一
案例:用户与用户详情
关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
3.8.2 合并查询
例:
1 | select * from employee, dept; |
合并查询的结果是笛卡尔积:两个集合A集合和B集合的所有组合情况(在多表查询时,需要消除无效的笛卡尔积)
消除无效笛卡尔积:
1 | select * from employee, dept where employee.dept = dept.id; |
3.8.3 内连接查询
内连接查询的是两张表交集的部分
隐式内连接:
1
SELECT 字段列表 FROM 表1, 表2 WHERE 条件 ...;
显式内连接:
1
SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ...;
例:
1 | -- 查询员工姓名,及关联的部门的名称 |
3.8.4 外连接查询
左外连接:查询左表所有数据,以及两张表交集部分数据
1
SELECT 字段列表 FROM 表1 LEFT [ OUTER ] JOIN 表2 ON 条件 ...;
右外连接:查询右表所有数据,以及两张表交集部分数据
1
SELECT 字段列表 FROM 表1 RIGHT [ OUTER ] JOIN 表2 ON 条件 ...;
例:
1 | -- 左 |
OUTER 可省略
3.8.5 自连接查询
当前表与自身的连接查询,自连接必须使用表别名
1 | SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ...; |
自连接查询,可以是内连接查询,也可以是外连接查询
例:
1 | -- 查询员工及其所属领导的名字 |
3.8.6 联合查询 union,union all
把多次查询的结果合并,形成一个新的查询集
1 | SELECT 字段列表 FROM 表A ... |
- UNION ALL 会有重复结果,UNION 不会
- 联合查询比使用 or 效率高,不会使索引失效
3.8.7 子查询
SQL语句中嵌套SELECT语句,称谓嵌套查询,又称子查询。
1 | SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2); |
子查询外部的语句可以是 INSERT / UPDATE / DELETE / SELECT 的任何一个
根据子查询结果可以分为:
- 标量子查询(子查询结果为单个值)
- 列子查询(子查询结果为一列)
- 行子查询(子查询结果为一行)
- 表子查询(子查询结果为多行多列)
根据子查询位置可分为:
- WHERE 之后
- FROM 之后
- SELECT 之后
3.8.7.1 标量子查询
子查询返回的结果是单个值(数字、字符串、日期等)。
常用操作符:- < > > >= < <=
例:
1 | -- 查询销售部所有员工 |
3.8.7.2 列子查询
返回的结果是一列(可以是多行)。
常用操作符:
操作符 | 描述 |
---|---|
IN | 在指定的集合范围内,多选一 |
NOT IN | 不在指定的集合范围内 |
ANY | 子查询返回列表中,有任意一个满足即可 |
SOME | 与ANY等同,使用SOME的地方都可以使用ANY |
ALL | 子查询返回列表的所有值都必须满足 |
例:
1 | -- 查询销售部和市场部的所有员工信息 |
3.8.7.3 行子查询
返回的结果是一行(可以是多列)。
常用操作符:=, <, >, IN, NOT IN
例:
1 | -- 查询与xxx的薪资及直属领导相同的员工信息 |
3.8.7.4 表子查询
返回的结果是多行多列
常用操作符:IN
例:
1 | -- 查询与xxx1,xxx2的职位和薪资相同的员工 |
3.9 事务
事务是一组操作的集合,事务会把所有操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
1 | -- 查看事务提交方式 |
通过
SET @@AUTOCOMMIT = 0
或start transaction
或BEGIN
开启事务。
例:
1 | SET @@AUTOCOMMIT = 0; # 开启事务 |
3.9.1 四大特性 ACID
- 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
- 一致性(Consistency):事务完成时,必须使所有数据都保持一致状态
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
3.9.2 并发事务
问题 | 描述 |
---|---|
脏读 | 一个事务读到另一个事务还没提交的数据 |
不可重复读 | 一个事务先后读取同一条记录,但两次读取的数据不同 |
幻读 | 一个事务按照条件查询数据时,没有对应的数据行,但是再插入数据时,又发现这行数据已经存在 |
更新丢失 | 多个事务对相同额数据操作,最后的更新覆盖了由其它事务所做的更新 |
脏读、幻读、不可重复读都是在事务A在做修改,事务B读取时出现的出现的数据一致性问题,这类问题可以由数据库提供的事务隔离机制来解决。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable Read(默认) | × | × | √ |
Serializable | × | × | × |
Serializable 性能最低,Read uncommitted 性能最高,数据安全性最差
查看事务隔离级别:
1 | SELECT @@TRANSACTION_ISOLATION; |
设置事务隔离级别:
1 | SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }; |
丢失更新发生在同一时间不同的事务都在对相同的数据进行修改,最后的提交覆盖了其它的提交,
MYSQL INNODB 锁机制:https://zhuanlan.zhihu.com/p/127919778
丢失更新解决方案一:悲观锁(排他锁)
sql 语句结尾加上for update
例:
1 | select * from t_account t wheret.id='1' for update |
丢失更新解决方案二:乐观锁
乐观锁的原理:给表添加一个 version 字段,进行数据修改时,判断 version 字段是否与原来的一致,只有一致才能修改成功,否则失败。
4. 进阶篇
4.1 存储引擎
MYSQL 体系结构
连接层:
最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层:
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。
引擎层:
存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
存储层:
主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表而不是基于库的,所以存储引擎也可以被称为表引擎;默认存储引擎是InnoDB。
相关操作:
1 | -- 查询建表语句 |
4.1.1 InnoDB
InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 引擎。
特点:
- DML 操作遵循 ACID 模型,支持事务
- 支持基于索引的行级锁,提高并发访问性能
- 支持外键约束,保证数据的完整性和正确性
文件:
xxx.ibd: xxx代表表名,InnoDB 引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引。
1 | show variables like 'innodb_file_per_table' |
innodb_file_per_table:开启后,每张表内的数据可以单独放到一个表空间内,不过单独的表空间里面只存放数据、索引和插入缓冲Bitmap页,其他信息,如插入缓冲的索引页,回滚信息,锁信息,二次写缓冲等还是存放在原来的共享表空间里。
从idb文件提取表结构数据:
1 | ibd2sdi xxx.ibd |
InnoDB 逻辑存储结构:
表空间(tablespace):所有的数据都被逻辑地存放在表空间中,同一个数据库的表可以共享一个表空间也可以拥有各自私有的表空间,私有表空间存储数据、索引、和插入缓冲BitMap页,其他的数据如回滚(undo)信息、系统事务信息等还是存储在共享表空间里;
段(segment):表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等;数据段其实就是InnoDB索引底层实现结构的B+树的叶子结点(leaf node segment),索引段即为B+树的非索引结点(Non-leaf node segement),回滚段则比较特殊;
区(extend):区是由连续页组成,区的大小固定为1MB(2^10 KB),页的大小默认为16KB,一个区中有64个连续的页;
页(page):区可以继续拆分为页,页是Innodb存储的最基本结构,也是Innodb磁盘管理的最小单位,默认页的大小为 16 KB,可通过 innodb_page_size 修改。
1
SHOW VARIABLES LIKE "innodb_page_size"; # 查看每页的大小
常见页的类型:
- 数据页(B-Tree Node)
- Undo页(Undo Log Page)
- 系统页(System Page)
- 事务数据页(Transaction System Page)
- 插入缓冲位图页(insert buffer bitmap)
- 插入缓冲空闲列表页(insert buffer free list)
- 未压缩的二进制大对象页(uncompressed blob page)
- 压缩的二进制大对象页(compressed blob page)
行(row):在InnoDB中,数据是按照行的格式来存储的。
4.1.2 MyISAM
MyISAM 是 MySQL 早期的默认存储引擎。
特点:
- 不支持事务,不支持外键
- 支持表锁,不支持行锁
- 访问速度快
文件:
- xxx.sdi: 存储表结构信息
- xxx.MYD: 存储数据
- xxx.MYI: 存储索引
4.1.3 Memory
Memory 引擎的表数据是存储在内存中的,受硬件问题、断电问题的影响,只能将这些表作为临时表或缓存使用。
特点:
- 存放在内存中,速度快
- hash索引(默认)
文件:
- xxx.sdi: 存储表结构信息
4.1.4 存储引擎特点
特点 | InnoDB | MyISAM | Memory |
---|---|---|---|
存储限制 | 64TB | 有 | 有 |
事务安全 | 支持 | - | - |
锁机制 | 行锁 | 表锁 | 表锁 |
B+tree索引 | 支持 | 支持 | 支持 |
Hash索引 | - | - | 支持 |
全文索引 | 支持(5.6版本之后) | 支持 | - |
空间使用 | 高 | 低 | N/A |
内存使用 | 高 | 低 | 中等 |
批量插入速度 | 低 | 高 | 高 |
支持外键 | 支持 |
4.1.5 存储引擎的选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
- InnoDB: 如果应用对事物的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,则 InnoDB 是比较合适的选择
- MyISAM: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不高,那这个存储引擎是非常合适的。
- Memory: 将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。Memory 的缺陷是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性
电商中的足迹和评论适合使用 MyISAM 引擎,缓存适合使用 Memory 引擎。
4.2 性能分析
4.2.1 查看执行频次
查看当前数据库的 INSERT, UPDATE, DELETE, SELECT 访问频次:
1 | SHOW GLOBAL STATUS LIKE 'Com_______'; |
或者
1 | SHOW SESSION STATUS LIKE 'Com_______'; |
4.2.2 慢查询日志
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件/etc/my.cnf
中配置如下信息:
slow_query_log=1 # 开启慢查询日志开关
long_query_time=2 # 设置慢查询日志的时间为2秒
更改后重启MySQL服务,日志文件位置:/var/lib/mysql/localhost-slow.log
查看慢查询日志开关状态:
1 | show variables like "slow_query_log"; |
4.3.3 profile
官方文档:https://dev.mysql.com/doc/refman/8.0/en/show-profile.html
profile 是 mysql 提供可以用来分析当前会话中语句执行的资源消耗情况。
查看是否支持及开关状态
1 | Show variables like "%profiling%"; |
开启 profile
1 | set profiling=1; |
查看所有语句耗时
1 | show profiles; |
查看指定query_id的SQL语句各个阶段的耗时
1 | show profile for query query_id; |
查看指定query_id的SQL语句CPU的使用情况
1 | show profile cpu for query query_id; |
查看指定query_id的SQL语句所有资源的使用情况
1 | show profile all for query query_id; |
4.3.4 explain
EXPLAIN 用于获取 MySQL 中 SELECT 语句的执行信息,EXPLAIN 只能解析 SELECT 查询。
1 | EXPLAIN SELECT * from user_info WHERE id < 300; |
EXPLAIN 各字段含义:
id:select 查询的序列号,表示查询中执行 select 子句或者操作表的顺序(id相同,执行顺序从上到下;id不同,值越大越先执行)
select_type:表示 SELECT 的类型,常见取值如下:
- SIMPLE:表示此查询不包含 UNION 查询或子查询
- SUBQUERY:包含在 Select 列表中的子查询,也就是不在 FROM 子句中的子查询
- DERIVED:表示包含在 From 子句中的 Select 查询
- UNION:表示此查询是 UNION 的第二和随后的查询
- UNION RESULT: 从 UNION 匿名临时表检索结果的 SELECT
- PRIMARY:表示此查询是最外层的查询
- DEPENDENT UNION: UNION 中的第二个或后面的查询语句, DEPENDENT 意味着 Select 依赖于外层查询中发现的数据
- DEPENDENT SUBQUERY: 包含在 Select 列表中的子查询, 但子查询依赖于外层查询的结果
type:表示连接类型,性能关系如下:
- ALL:全表扫描
- index :Full index Scan,ALL 和 index 都是读全表,但是index 类型只遍历索引树,而 all 是从硬盘中读取
- range :只检索给定范围的行
- ref :为唯一性索引扫描,返回匹配某个单独值的所有行
- eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配
- const:表示通过索引一次就找到了
- System:表只有一行记录
1
ALL < index < range < ref < eq_ref < const < system < NULL
possible_key:此次查询中可能选用的索引,但不一定都被使用
Key:实际使用的索引,如果为 NULL,则没有使用索引
Key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
rows:根据统计信息及索引的选用情况,大致估计出找到所需记录所需要读取的行数,可能并不总是准确的
filtered:表示返回结果的行数占需读取行数的百分比,filtered 的值越大越好
Extra:包含不适合在其他列中显示但是十分重要的额外信息
- Using filesort:说明 mysql 会根据使用一个外部索引排序,而不是按照表内的索引顺序进行读取。
- Using temporary:使用了临时表保存中间的结果,MYSQL在对查询结果排序的时候使用了临时表。常见于排序order by 和分组查询group by。
- Using index:表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率不错;如果同时出现using where,表明索引被用来执行键值的查找,索引用来读取数据而非执行查找动作。
- Using join buffer:使用了连接缓存。
- Impossible where:where 子句的值总是 false,不能用来获取任何元组。
4.3 索引
官网:https://dev.mysql.com/doc/refman/8.0/en/mysql-indexes.html
索引是帮助 MySQL 高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查询算法,这种数据结构就是索引。
优点:
- 提高数据检索效率,降低数据库的 IO 成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗
缺点:
- 索引也是要占用空间的
- 索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE
索引本身很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上
MYSQL 默认存储引擎 INNODB 只显式支持 B Tree( 从技术上来说是 B+Tree)索引。
4.3.1 索引结构
以索引的存储结构划分
索引结构 | 描述 |
---|---|
B Tree | B tree 或 B+tree,最常见的索引类型,大部分引擎都支持 B+tree 索引 |
Hash | 底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询 |
R-Tree(空间索引) | 空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full-Text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES |
4.3.1.1 B Tree(B-Tree)
B-tree 是一种多路自平衡搜索树,它类似普通的二叉树,但是 B-tree 允许每个节点有更多的子节点
上图中 max-degree=3(每个节点最大子节点/指针个数为3)
B Tree 的特点:
- 数据分布在各个节点中,所以搜索可能在非叶子节点结束;
- 原理为根据主键进行二分查找,如果命中则结束。
4.3.1.2 B+Tree
B+Tree 的特点(与B-Tree 的区别):
https://blog.csdn.net/XiaoGong1688/article/details/100150759
- 仅叶子节点存储数据,不可能在非叶子节点命中;
- 每个叶子节点增加了一个指向相邻叶子节点的指针,形成了一个有序链表,范围查询比 B-Tree 更快,更简便;
- 因为非叶子节点不存储数据,所以同样大小的磁盘页可以容纳更多的节点,查询的 I/O 次数也更少;
4.3.1.3 Hash
哈希索引基于 hash 表,采用 hash 算法计算出 索引列(创建 hash索引时指定)的 hashcode(也就是 key),而值为指针,指向数据在内存中的地址,如果两个或多个索引列计算出的 hashcode 一样,这就产生了 hash 碰撞(hash 冲突),mysql 使用 链地址法 来解决这个冲突。
特点:
- Hash索引只能用于等值查询(=、in),不支持范围查询(betwwn、>、<、…);
- 无法利用索引完成排序操作;
- 查询效率高,通常只需要一次检索就可以了,效率通常要高于 B+Tree 索引;
- 如果 hash 算法设计不好,碰撞过多,性能也会变差。
存储引擎支持:
- Memory
- InnoDB:对于频繁访问的索引页,innodb 会建立自适应 hash索引,即在 B Tree 索引基础上建立 hash 索引。
为什么使用 B+Tree:https://www.mycookies.cn/mysql/mysql-index-2.html#
4.3.2 索引分类
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 根据主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
普通索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
根据物理存储方式划分:
分类 | 含义 | 特点 |
---|---|---|
聚集索引(聚簇索引) | 将数据与索引放一块,索引结构的叶子节点保存了完整数据行 | 必须有,而且只有一个 |
二级索引(非聚集索引/辅助索引) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引;
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引;
- 如果表没有主键或没有合适的唯一索引,则 InnoDB 会自动生成一个作为隐藏的字段作为聚集索引。
通过非主键索引查询数据时,会先查找到主键 id,然后根据 主键 id 再到主键索引上去查找对应的数据,这个过程叫做回表
覆盖索引指需要查询的字段正好是建立索引的字段,那么直接根据该索引,就可以查到数据了,而无需回表查询。
思考题
1. 以下 SQL 语句,哪个执行效率高?为什么?
1 | select * from user where id = 10; |
答:第一条语句,因为第二条需要回表查询,相当于两个步骤。
2. InnoDB 主键索引的 B+Tree 高度为多少?
答:假设一行数据大小为1k,一页中可以存储16行这样的数据。InnoDB 的指针占用6个字节的空间,主键假设为bigint,占用字节数为8.
可得公式:n * 8 + (n + 1) * 6 = 16 * 1024
,其中 8 表示 bigint 占用的字节数,n 表示当前节点存储的key的数量,(n + 1) 表示指针数量(比key多一个)。算出n约为1170。
如果树的高度为2,那么他能存储的数据量大概为:1171 * 16 = 18736
;
如果树的高度为3,那么他能存储的数据量大概为:1171 * 1171 * 16 = 21939856
。
另外,如果有成千上万的数据,那么就要考虑分表,涉及运维篇知识。
4.3.3 语法
创建索引:CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name, ...);
如果不加 CREATE 后面不加索引类型参数,则创建的是常规索引
查看索引:SHOW INDEX FROM table_name;
删除索引:DROP INDEX index_name ON table_name;
案例:
1 | -- name字段为姓名字段,该字段的值可能会重复,为该字段创建索引 |
4.3.4 使用规则
4.3.4.1 最左前缀匹配原则
最左前缀匹配原则指的是,在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成。
如果在执行过程中遇到范围查询,如 >、<、between 和 以%开头的 like 查询 等条件,才会停止匹配。
使用表中的多个字段创建索引,就是 联合索引,也叫 组合索引 或 复合索引。
最左前缀匹配按例:https://cloud.tencent.com/developer/article/1774781
4.3.4.2 索引失效情况
- 在联合索引场景下,查询条件不满足最左前缀匹配原则;
- 使用了
select *
,应该避免使用select *
,尽量使用覆盖索引; - 索引列参与了运算、函数,或进行了类型转换,导致索引失效转向全表扫描;
- 模糊查询时(like语句),通配符位于条件的首部;
- 用 or 分割开的条件,如果 or 其中一个条件的列没有索引,那么涉及的索引都不会被用到;
- 使用了不等于(!= 、 <>);
- 使用了 is null、is not null;
- 字符串不加单引号或双引号导致索引失效;
- 查询条件使用 not in 时,如果是主键则走索引,如果是普通索引,则索引失效;
- 查询条件使用 not exists 时,索引失效;
- 如果 MySQL 评估使用索引比全表扫描更慢,则不使用索引。
4.3.4.3 覆盖索引&回表查询
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能找到),减少 select *。
explain 中 extra 字段含义:using index condition
:查找使用了索引,但是需要回表查询数据using where; using index;
:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询
如果在聚集索引中直接能找到对应的行,则直接返回行数据,只需要一次查询,哪怕是select *;如果在辅助索引中找聚集索引,如select id, name from xxx where name='xxx';
,也只需要通过辅助索引(name)查找到对应的id,返回name和name索引对应的id即可,只需要一次查询;如果是通过辅助索引查找其他字段,则需要回表查询,如select id, name, gender from xxx where name='xxx';
所以尽量不要用select *
,容易出现回表查询,降低效率,除非有联合索引包含了所有字段
面试题:一张表,有四个字段(id, username, password, status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:select id, username, password from tb_user where username='itcast';
解:给username和password字段建立联合索引,则不需要回表查询,直接覆盖索引
4.3.4.4 前缀索引
当字段类型为字符串(varchar, text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率,此时可以只降字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
语法:create index idx_xxxx on table_name(columnn(n));
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
求选择性公式:
1 | select count(distinct email) / count(*) from tb_user; |
show index 里面的sub_part可以看到接取的长度。
4.3.5 设计原则
- 针对于数据量较大,且查询比较频繁的表建立索引
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
- 如果是字符串类型的字段,字段长度较长,可以针对于字段的特点,建立前缀索引
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率
- 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询
4.4 SQL 优化
4.4.1 插入数据
大批量插入:
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令插入。
1 | # 客户端连接服务端时,加上参数 --local-infile(这一行在bash/cmd界面输入) |
4.4.2 主键优化
数据组织方式:在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(Index organized table, IOT)
页分裂:页可以为空,也可以填充一般,也可以填充100%,每个页包含了2-N行数据(如果一行数据过大,会行溢出),根据主键排列。
页合并:当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录到达 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前后)看看是否可以将这两个页合并以优化空间使用。
MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或创建索引时指定
文字说明不够清晰明了,具体可以看视频里的PPT演示过程:https://www.bilibili.com/video/BV1Kr4y1i7ru?p=90
主键设计原则:
- 满足业务需求的情况下,尽量降低主键的长度
- 插入数据时,尽量选择顺序插入,选择使用 AUTO_INCREMENT 自增主键
- 尽量不要使用 UUID 做主键或者是其他的自然主键,如身份证号
- 业务操作时,避免对主键的修改
4.4.3 Order by 优化
- Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序
- Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高
如果order by字段全部使用升序排序或者降序排序,则都会走索引,但是如果一个字段升序排序,另一个字段降序排序,则不会走索引,explain的extra信息显示的是Using index, Using filesort
,如果要优化掉Using filesort,则需要另外再创建一个索引,如:
1 | create index idx_user_age_phone_ad on tb_user(age asc, phone desc); |
总结:
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
- 尽量使用覆盖索引
- 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
- 如果不可避免出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)
4.4.4 group by 优化
- 在分组操作时,可以通过索引来提高效率
- 分组操作时,索引的使用也是满足最左前缀法则的
如索引为idx_user_pro_age_stat
,则句式可以是select ... where profession order by age
,这样也符合最左前缀法则
4.4.5 limit优化
常见的问题如limit 2000000, 10
,此时需要 MySQL 排序前2000000条记录,但仅仅返回2000000 - 2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化方案:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化
例如:
1 | -- 此语句耗时很长 |
4.4.6 count 优化
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高(前提是不适用where);
InnoDB 在执行 count(*) 时,需要把数据一行一行地从引擎里面读出来,然后累计计数。
优化方案:自己计数,如创建key-value表存储在内存或硬盘,或者是用redis
count的几种用法:
- 如果count函数的参数(count里面写的那个字段)不是NULL(字段值不为NULL),累计值就加一,最后返回累计值
- 用法:count(*)、count(主键)、count(字段)、count(1)
- count(主键)跟count(*)一样,因为主键不能为空;count(字段)只计算字段值不为NULL的行;count(1)引擎会为每行添加一个1,然后就count这个1,返回结果也跟count(*)一样;count(null)返回0
各种用法的性能:
- count(主键):InnoDB引擎会遍历整张表,把每行的主键id值都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加(主键不可能为空)
- count(字段):没有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加;有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加
- count(1):InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一层,放一个数字 1 进去,直接按行进行累加
- count(*):InnoDB 引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加
按效率排序:count(字段) < count(主键) < count(1) < count(*),所以尽量使用 count(*)
4.4.7 update 优化
InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。
如以下两条语句:update student set no = '123' where id = 1;
,这句由于id有主键索引,所以只会锁这一行;update student set no = '123' where name = 'test';
,这句由于name没有索引,所以会把整张表都锁住进行数据更新,解决方法是给name字段添加索引。
P 90
数据类型
整型
类型名称 | 取值范围 | 大小 |
---|---|---|
TINYINT | -128〜127 | 1个字节 |
SMALLINT | -32768〜32767 | 2个宇节 |
MEDIUMINT | -8388608〜8388607 | 3个字节 |
INT (INTEGHR) | -2147483648〜2147483647 | 4个字节 |
BIGINT | -9223372036854775808〜9223372036854775807 | 8个字节 |
无符号在数据类型后加 unsigned 关键字。
浮点型
类型名称 | 说明 | 存储需求 |
---|---|---|
FLOAT | 单精度浮点数 | 4 个字节 |
DOUBLE | 双精度浮点数 | 8 个字节 |
DECIMAL (M, D),DEC | 压缩的“严格”定点数 | M+2 个字节 |
日期和时间
类型名称 | 日期格式 | 日期范围 | 存储需求 |
---|---|---|---|
YEAR | YYYY | 1901 ~ 2155 | 1 个字节 |
TIME | HH:MM:SS | -838:59:59 ~ 838:59:59 | 3 个字节 |
DATE | YYYY-MM-DD | 1000-01-01 ~ 9999-12-3 | 3 个字节 |
DATETIME | YYYY-MM-DD HH:MM:SS | 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 | 8 个字节 |
TIMESTAMP | YYYY-MM-DD HH:MM:SS | 1980-01-01 00:00:01 UTC ~ 2040-01-19 03:14:07 UTC | 4 个字节 |
字符串
类型名称 | 说明 | 存储需求 |
---|---|---|
CHAR(M) | 固定长度非二进制字符串 | M 字节,1<=M<=255 |
VARCHAR(M) | 变长非二进制字符串 | L+1字节,在此,L< = M和 1<=M<=255 |
TINYTEXT | 非常小的非二进制字符串 | L+1字节,在此,L<2^8 |
TEXT | 小的非二进制字符串 | L+2字节,在此,L<2^16 |
MEDIUMTEXT | 中等大小的非二进制字符串 | L+3字节,在此,L<2^24 |
LONGTEXT | 大的非二进制字符串 | L+4字节,在此,L<2^32 |
ENUM | 枚举类型,只能有一个枚举字符串值 | 1或2个字节,取决于枚举值的数目 (最大值为65535) |
SET | 一个设置,字符串对象可以有零个或 多个SET成员 | 1、2、3、4或8个字节,取决于集合 成员的数量(最多64个成员) |
二进制类型
类型名称 | 说明 | 存储需求 |
---|---|---|
BIT(M) | 位字段类型 | 大约 (M+7)/8 字节 |
BINARY(M) | 固定长度二进制字符串 | M 字节 |
VARBINARY (M) | 可变长度二进制字符串 | M+1 字节 |
TINYBLOB (M) | 非常小的BLOB | L+1 字节,在此,L<2^8 |
BLOB (M) | 小 BLOB | L+2 字节,在此,L<2^16 |
MEDIUMBLOB (M) | 中等大小的BLOB | L+3 字节,在此,L<2^24 |
LONGBLOB (M) | 非常大的BLOB | L+4 字节,在此,L<2^32 |