MySQL 总结|MySQL 从入门到高级
摘要:本文全面系统地讲解了 MySQL 从基础操作到高级特性的核心知识,内容涵盖数据库基础概念、SQL 核心语法、索引与性能优化、事务与锁机制、InnoDB 引擎原理、主从复制等关键技术。


1. MySQL 概述
1.1 数据库相关概念
本部分将讲解三个核心概念:数据库、数据库管理系统、SQL。
| 名称 | 全称 | 简称 |
|---|---|---|
| 数据库 | 存储数据的仓库,数据是有组织的进行存储 | DataBase |
| 数据库管理系统 | 操纵和管理数据库的大型软件 | DataBase Management System |
| SQL | 操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准 | Structured Query Language |
2. SQL语句
全称 Structured Query Language(结构化查询语言),是操作关系型数据库的编程语言,定义了一套操作关系型数据库的统一标准。无论使用 Oracle、SQL Server 还是 MySQL 等关系型数据库,均可以通过 SQL 语言进行统一操作,因此掌握 SQL 可灵活适配不同关系型数据库场景。
2.1 SQL 通用语法
在学习具体 SQL 语句前,需先了解 SQL 语言的通用规则:
- SQL 语句可单行或多行书写,必须以分号结尾;
- 可使用空格 / 缩进来增强语句可读性(不影响执行结果);
- MySQL 中的 SQL 语句不区分大小写,建议关键字使用大写,提高辨识度;
2.2 SQL 分类
根据功能,SQL 语句主要分为四类:DDL、DML、DQL、DCL,具体说明如下:
| 分类 | 说明 |
|---|---|
| DDL | 用于定义数据库对象(数据库、表、字段),如创建 / 删除数据库、创建 / 修改表结构等 |
| DML | 用于对数据库表中的数据进行增、删、改操作 |
| DQL | 用于查询数据库表中的记录,是业务中使用频次最高的 SQL 类型 |
| DCL | 用于管理数据库用户、控制数据库访问权限,如创建用户、授予 / 撤销权限等 |
2.3 DDL(数据定义语言)
DDL 主要用于操作数据库、表、字段等 “结构级” 对象,核心是 “定义结构”,不涉及具体数据。
2.3.1 数据库操作
1. 查询所有数据库
show databases;执行结果会显示当前 MySQL 服务器中所有已创建的数据库,例如系统默认数据库 information_schema、mysql、sys 等。
2. 查询当前数据库
select database();用于确认当前所处/正在使用的数据库上下文(若未切换数据库,返回 NULL)。
3. 创建数据库
create database [ if not exists ] 数据库名 [ default charset 字符集 ] [ collate 排序规则 ];易错点:数据库名称在【 if not exists 】后面
if not exists:可选参数,避免重复创建同名数据库导致报错(若数据库已存在则不执行);default charset:可选参数,指定数据库默认字符集;collate:可选参数,指定字符排序规则(通常默认跟随字符集,无需手动设置)。
4. 删除数据库
drop database [ if exists ] 数据库名;if exists:可选参数,避免删除不存在的数据库导致报错(若数据库不存在则不执行操作)。
5. 切换数据库
use 数据库名;操作某数据库下的表前,必须先通过 use 切换到该数据库上下文。
2.3.2 表操作
2.3.2.1 表操作 - 查询创建
1. 查询所有表
show tables;需先通过 use 切换到目标数据库,否则无法查询表。
2. 查看指定表结构
desc 表名;返回表的字段名、数据类型、是否允许为 NULL、主键 / 外键、默认值等结构信息。
3. 查询指定表建表语句
show create table 表名;返回创建该表的完整 SQL 语句,包含默认存储引擎(如 InnoDB)、字符集等隐式配置。
4. 创建表结构
CREATE TABLE 表名( 字段1 字段1类型 [ COMMENT 字段1注释 ], 字段2 字段2类型 [ COMMENT 字段2注释 ], ... 字段n 字段n类型 [ COMMENT 字段n注释 ] ) [ COMMENT 表注释 ];- 字段定义格式:
字段名 类型 [约束] [注释]; - 最后一个字段后不能加逗号,否则会语法报错;
COMMENT:可选参数,用于添加字段 / 表的说明,提高可读性。
2.3.2.2 表操作 - 数据类型
MySQL 中的数据类型主要分为三类:数值类型、字符串类型、日期时间类型,需根据业务场景选择合适的类型(如年龄用 TINYINT,手机号用 CHAR(11))。
1. 数值类型
适用于存储整数、小数等数值数据,核心是 “节省存储空间” 和 “保证精度”。
| 类型 | 大小 | 有符号(SIGNED)范围 | 无符号(UNSIGNED)范围 | 描述 |
|---|---|---|---|---|
| TINYINT | 1byte | (-128, 127) | (0, 255) | 小整数值(如年龄、状态码) |
| SMALLINT | 2bytes | (-32768, 32767) | (0, 65535) | 中整数值(如数量较少的 ID) |
| MEDIUMINT | 3bytes | (-8388608, 8388607) | (0, 16777215) | 大整数值 |
| INT/INTEGER | 4bytes | (-2147483648, 2147483647) | (0, 4294967295) | 常用大整数值(如用户 ID、订单 ID) |
| BIGINT | 8bytes | (-2^63, 2^63-1) | (0, 2^64-1) | 极大整数值(如海量数据的 ID) |
| FLOAT | 4bytes | (-3.4E+38, 3.4E+38) | 0 和 (1.2E-38, 3.4E+38) | 单精度浮点数(精度较低,不适合金额) |
| DOUBLE | 8bytes | (-1.8E+308, 1.8E+308) | 0 和 (2.2E-308, 1.8E+308) | 双精度浮点数(精度较高,仍不适合金额) |
| DECIMAL | 可变 | 依赖 M(精度)和 D(标度) | 依赖 M(精度)和 D(标度) | 定点小数(精确存储,适合金额、分数等) |
使用建议:
- 年龄:
TINYINT UNSIGNED(无负数,范围 0-255 足够); - 分数(如满分 100,1 位小数):
DOUBLE(4,1)(总长度 4,小数位 1,范围 0.0-100.0); - 金额(如保留 2 位小数):
DECIMAL(10,2)(避免浮点数精度丢失)。
2. 字符串类型
适用于存储文本、符号等字符数据,核心是 “区分定长与变长”。
| 类型 | 大小 | 描述 |
|---|---|---|
| CHAR | 0-255 bytes | 定长字符串(指定长度后,无论实际值多长,均占用固定字节) |
| VARCHAR | 0-65535 bytes | 变长字符串(仅占用实际值长度 + 1-2 字节的长度标识,节省空间) |
| TINYTEXT | 0-255 bytes | 短文本(适合存储少量文字,如备注) |
| TEXT | 0-65535 bytes | 长文本(适合存储文章、评论等) |
| MEDIUMTEXT | 0-16777215 bytes | 中等长度文本 |
| LONGTEXT | 0-4294967295 bytes | 极大文本(适合存储超大文本,如日志) |
| TINYBLOB/BLOB/MEDIUMBLOB/LONGBLOB | 同对应 TEXT 类型 | 二进制数据(如图片、文件,不推荐直接存储,建议存文件路径) |
CHAR 与 VARCHAR 区别:
CHAR(10):存储 “abc” 时,仍占用 10 字节(空格填充),性能高;VARCHAR(10):存储 “abc” 时,仅占用 3 + 1 = 4 字节(1 字节标识长度),节省空间。
使用建议:
- 用户名(长度不定,最长 50):
VARCHAR(50); - 性别(固定值 “男 / 女”):
CHAR(1); - 手机号(固定 11 位):
CHAR(11)。
3. 日期时间类型
适用于存储日期、时间或混合时间数据,核心是 “匹配业务场景精度”。
| 类型 | 大小 | 范围 | 格式 | 描述 |
|---|---|---|---|---|
| DATE | 3bytes | 1000-01-01 至 9999-12-31 | YYYY-MM-DD | 仅日期(如生日) |
| TIME | 3bytes | -838:59:59 至 838:59:59 | HH:MM:SS | 仅时间(如打卡时间) |
| YEAR | 1byte | 1901 至 2155 | YYYY | 仅年份(如毕业年份) |
| DATETIME | 8bytes | 1000-01-01 00:00:00 至 9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 日期 + 时间(如订单创建时间,不依赖时区) |
| TIMESTAMP | 4bytes | 1970-01-01 00:00:01 至 2038-01-19 03:14:07 | YYYY-MM-DD HH:MM:SS | 日期 + 时间(依赖时区,自动更新,适合记录 “最后修改时间”) |
使用建议:
- 生日:
DATE; - 订单创建时间:
DATETIME; - 数据最后修改时间:
TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(自动更新)。
2.3.2.3 表操作 - 改表结构
1. 添加字段
ALTER TABLE 表名 ADD 字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];2. 修改数据类型
ALTER TABLE 表名 MODIFY 字段名 新数据类型 (长度);3. 改字段名和数据类型
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];可同时修改字段名和类型,需注意新旧字段名的顺序。
4. 删除字段
ALTER TABLE 表名 DROP 字段名;5. 修改表名
ALTER TABLE 表名 RENAME TO 新表名;核心:ALTER TABLE 表名 [ 操作 ] 字段名 类型
2.3.2.4 表操作 - 删除
1. 删除表
DROP TABLE [ IF EXISTS ] 表名;if exists:避免删除不存在的表导致报错。2. 删除表并重新创建
TRUNCATE TABLE 表名;- 效果:删除表中所有数据,但保留表结构(相当于 “清空表”);
- 注意:数据无法恢复,且自增字段会重置为初始值(如 1)。
2.5 DML(数据操作语言)
DML 用于对表中的数据进行 “增、删、改” 操作,核心是 “操作数据”,不改变表结构。
2.5.1 添加数据(INSERT)
1. 给指定字段添加数据
INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...);- 字段名与值必须一一对应(顺序、数量、类型需匹配);
- 字符串和日期类型的值必须用单引号包裹;
- 字段值需符合表结构约束。
2. 给全部字段添加数据
INSERT INTO 表名 VALUES (值1, 值2, ...);- 需按表中字段的默认顺序传入所有字段的值,不推荐(表结构变更后易报错)。
3. 批量添加数据
-- 方式1:指定字段批量插入 INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...); -- 方式2:全部字段批量插入 INSERT INTO 表名 VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...);注意事项
- 字段顺序与值顺序必须一致;
- 字符串、日期类型必须用单引号包裹;
- 插入的值需符合字段约束(如长度、范围、非空等),否则报错。
2.5.2 修改数据(UPDATE)
UPDATE 表名 SET 字段名1 = 值1, 字段名2 = 值2, ... [ WHERE 条件 ];WHERE:可选参数,用于指定修改的范围(若省WHERE,则修改表中所有数据,谨慎)。
注意事项
- 无
WHERE条件会修改全表数据,生产环境中需严格校验条件; - 可同时修改多个字段,用逗号分隔字段赋值表达式。
2.5.3 删除数据(DELETE)
DELETE FROM 表名 [ WHERE 条件 ];WHERE:可选参数,指定删除的范围(若省略WHERE,则删除表中所有数据!)。
注意事项
- 无
WHERE条件会删除全表数据,且数据无法通过ROLLBACK恢复(除非开启事务); DELETE只能删除行数据,不能删除单个字段的值(需用UPDATE将字段设为NULL);- DataGrip 等工具会对删除全表操作进行二次确认,避免误操作。
2.6 DQL(数据查询语言)
DQL 用于查询表中的记录,是业务中最常用的 SQL 类型,支持条件筛选、排序、分组、分页等复杂操作。
2.6.1 数据准备
先创建 emp 员工表并插入测试数据,后续案例基于该表展开。
2.6.2 DQL 基本语法
完整 DQL 语法结构(按编写顺序):
SELECT 字段列表 -- 要查询的字段(如 name, age 或 * 表示所有字段) FROM 表名列表 -- 要查询的表(单表查询仅需写表名) WHERE 条件列表 -- 行级筛选条件(如 age > 20) GROUP BY 分组字段列表 -- 按指定字段分组(如按 gender 分组) HAVING 分组后条件列表 -- 分组后的筛选条件(如 count(*) > 5) ORDER BY 排序字段列表 -- 按指定字段排序(如 age ASC 升序) LIMIT 分页参数 -- 分页查询(如 LIMIT 0, 10 表示第 1 页,10 条/页)执行顺序(与编写顺序不同,需重点理解):
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
2.6.3 基础查询
1. 查询多个指定字段
SELECT 字段1, 字段2, ... FROM 表名;2. 查询所有字段
SELECT * FROM 表名;缺点:不直观、影响效率(若表字段多,会冗余返回不必要的字段),生产环境建议明确指定字段。
3. 字段设置别名
-- 方式1:使用 AS(推荐,可读性高) SELECT 字段1 AS 别名1, 字段2 AS 别名2 FROM 表名; -- 方式2:省略 AS(简洁) SELECT 字段1 别名1, 字段2 别名2 FROM 表名;别名含空格或特殊字符时,需用单引号包裹。
4. 去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;仅当所有指定字段的值完全相同时,才会被视为重复记录并去重。
2.6.4 条件查询(WHERE)
通过 WHERE 子句筛选符合条件的行数据,支持比较运算符和逻辑运算符。
1. 常用比较运算符
| 比较运算符 | 功能 |
|---|---|
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
= | 等于 |
<> 或 != | 不等于 |
BETWEEN ... AND ... | 在指定范围(含边界) |
IN(...) | 在指定集合中 |
LIKE 占位符 | 模糊匹配 |
IS NULL | 为空 |
IS NOT NULL | 不为空 |
- 模糊匹配占位符:
`_`:匹配单个任意字符; `%`:匹配 0 个或多个任意字符。2. 常用逻辑运算符
| 逻辑运算符 | 功能 |
|---|---|
AND 或 && | 并且(多个条件同时成立) |
OR 或 ` | ` |
NOT 或 ! | 非(否定条件) |
2.6.5 聚合函数
聚合函数用于对一列数据进行 “纵向计算”(将列数据视为一个整体),返回单个结果值。
1. 常用聚合函数
| 函数 | 功能 |
|---|---|
count(字段) | 统计非 NULL 值的记录数 |
max(字段) | 求该列的最大值 |
min(字段) | 求该列的最小值 |
avg(字段) | 求该列的平均值 |
sum(字段) | 求该列的总和 |
注意:NULL 值不参与任何聚合函数计算。2. 语法
SELECT 聚合函数(字段列表) FROM 表名 [ WHERE 条件 ];2.6.6 分组查询
通过 GROUP BY 按指定字段分组,将相同值的行归为一组,然后对每组进行聚合计算(如统计每组人数、平均年龄)。
1. 语法
SELECT 分组字段, 聚合函数(字段) FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段 [ HAVING 分组后条件 ];WHERE:分组前的筛选条件(筛选行数据,不满足条件的行不参与分组);HAVING:分组后的筛选条件(筛选分组结果,不满足条件的分组不显示)。
注意:前后的第一个查询字段必须相同,都为分组字段,第二个是聚合函数才有意义
2. WHERE 与 HAVING
| 对比维度 | WHERE | HAVING |
|---|---|---|
| 执行时机 | 分组前 | 分组后 |
| 作用对象 | 行数据 | 分组结果 |
| 支持的条件 | 不能使用聚合函数 | 可以使用聚合函数 |
3. 注意事项
- 分组后查询的字段只能是 “分组字段” 或 “聚合函数”,查询其他字段无意义(结果不可控);
- 多字段分组时,按字段顺序依次分组(先按第一个字段分组,同组内再按第二个字段分组)。
2.6.7 排序查询
通过 ORDER BY 按指定字段对查询结果排序,支持升序和降序。
1. 语法
SELECT 字段列表 FROM 表名 [ WHERE 条件 ] [ GROUP BY 分组字段 ] [ HAVING 分组后条件 ] ORDER BY 字段1 排序方式1, 字段2 排序方式2 ...;- 排序方式:
`ASC`:升序(默认,可省略); `DESC`:降序;多字段排序:先按字段 1 排序,字段 1 值相同的行再按字段 2 排序。
2.6.8 分页查询
通过 LIMIT 实现分页查询(避免一次性返回大量数据,优化性能),仅 MySQL 支持(其他数据库如 Oracle 用 ROWNUM)。
1. 语法
SELECT 字段列表 FROM 表名 [ 其他子句 ] LIMIT 起始索引, 查询记录数;- 起始索引:从 0 开始(表示第 1 条数据的索引为 0);
- 计算公式:
起始索引 = (页码 - 1) * 每页记录数; - 若查询第 1 页数据,起始索引可省略。
2.6.9 DQL 执行顺序验证
通过别名测试可确认 DQL 执行顺序:
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
2.7 DCL(数据控制语言)
DCL 用于管理数据库用户和控制访问权限,主要由 DBA(数据库管理员)操作,开发人员使用较少。
2.7.1 管理用户
1. 查询用户
MySQL 中所有用户信息存储在系统数据库 mysql 的 user 表中:
select * from mysql.user;- 核心字段:
`Host`:用户可访问的主机(`localhost` 表示仅本地访问,`%` 表示任意主机访问); `User`:用户名; `authentication_string`:用户密码(加密存储)。- 注意:
Host + User唯一标识一个用户(同一用户名,不同主机视为不同用户)。
2. 创建用户
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';- 主机名:
`localhost`:仅允许本地(当前服务器)访问; `%`:允许任意主机访问(远程访问需开放 MySQL 端口 3306);密码:长度至少 4 位(MySQL 8.0+ 默认密码策略较严格,需包含字母、数字、符号)。
3. 修改用户密码
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql\_native\_password BY '新密码';mysql_native_password:指定密码加密方式(兼容旧版本客户端,MySQL 8.0+ 推荐)。4. 删除用户
DROP USER '用户名'@'主机名';注意事项
- 必须指定
主机名(即使是%),否则无法准确匹配用户; - 开发环境中避免创建
%主机的用户(安全风险),建议仅允许指定 IP 访问。
2.7.2 权限控制
MySQL 定义了多种权限,常用权限如下:
| 权限 | 说明 |
|---|---|
ALL 或 ALL PRIVILEGES | 所有权限 |
SELECT | 查询数据权限(仅 DQL) |
INSERT | 插入数据权限(仅 DML) |
UPDATE | 修改数据权限(仅 DML) |
DELETE | 删除数据权限(仅 DML) |
ALTER | 修改表结构权限(仅 DDL) |
DROP | 删除数据库 / 表 / 视图权限(仅 DDL) |
CREATE | 创建数据库 / 表权限(仅 DDL) |
1. 查询用户权限
SHOW GRANTS FOR '用户名'@'主机名';2. 授予权限
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';- 权限列表:多个权限用逗号分隔(如
SELECT, INSERT),ALL表示所有权限; - 数据库名。表名:
`*.*`:所有数据库的所有表(全局权限); `itcast.*`:`itcast` 数据库的所有表; `itcast.emp`:`itcast` 数据库的 `emp` 表。3. 撤销权限
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';注意事项
- 授予 / 撤销权限后,无需重启 MySQL,权限立即生效;
- 开发环境中遵循 “最小权限原则”(如仅给查询需求的用户授予
SELECT权限),避免权限过大导致安全风险。
3. 函数
函数是指一段可以直接被另一段程序调用的程序或代码。这意味着,在 MySQL 中已经为我们提供了这一段程序或代码,我们只需在合适的业务场景调用对应的函数,即可完成对应的业务需求。
MySQL 中的函数主要分为四类:字符串函数、数值函数、日期函数、流程函数
3.1 字符串函数
MySQL 中内置了诸多字符串函数,常用的几个如下:
| 函数 | 功能 |
|---|---|
| CONCAT(S1,S2,...Sn) | 字符串拼接,将 S1、S2、...、Sn 拼接成一个字符串 |
| LOWER(str) | 将字符串 str 全部转为小写 |
| UPPER(str) | 将字符串 str 全部转为大写 |
| LPAD(str,n,pad) | 左填充,用字符串 pad 对 str 的左边进行填充,使总长度达到 n 个字符 |
| RPAD(str,n,pad) | 右填充,用字符串 pad 对 str 的右边进行填充,使总长度达到 n 个字符 |
| TRIM(str) | 去掉字符串头部和尾部的空格 |
| SUBSTRING(str,start,len) | 返回从字符串 str 的 start 位置起,长度为 len 的子字符串 |
使用方法:select 函数
演示如下:
A. concat:字符串拼接 ⭐⭐⭐
select concat('Hello' , ' MySQL');B. lower:全部转小写
select lower('Hello');C. upper:全部转大写
select upper('Hello');D. lpad:左填充
select lpad('01', 5, '-');E. rpad:右填充
select rpad('01', 5, '-');F. trim:去除空格
select trim(' Hello MySQL ');G. substring:截取子字符串 ⭐
select substring('Hello MySQL',1,5);案例
由于业务需求变更,企业员工的工号统一改为 5 位数,目前不足 5 位数的需在前面补 0(例如:1 号员工的工号应改为 00001)。
update emp set workno = lpad(workno, 5, '0');处理完毕后,具体的数据如下:
| id | workno | name | gender | age | idcard | workaddress | entrydate |
|---|---|---|---|---|---|---|---|
| 1 | 00001 | 抖音号:1332402852 | 女 | 20 | 123456789012345678 | 北京 | 2000-01-01 |
| 2 | 00002 | 张无忌 | 男 | 18 | 123456789012345670 | 北京 | 2005-09-01 |
| 3 | 00003 | 韦一笑 | 男 | 38 | 123456789712345670 | 上海 | 2005-08-01 |
| 4 | 00004 | 赵敏 | 女 | 18 | 123456757123845670 | 北京 | 2009-12-01 |
| 5 | 00005 | 小昭 | 女 | 16 | 123456769012345678 | 上海 | 2007-07-01 |
| 6 | 00006 | 杨逍 | 男 | 28 | 12345678931234567X | 北京 | 2006-01-01 |
| 7 | 00007 | 抖音号:1232402856 | 男 | 40 | 123456789212345678 | 北京 | 2005-05-01 |
| 8 | 00008 | 黛绮丝 | 女 | 38 | 123456157123645670 | 天津 | 2015-05-01 |
| 9 | 00009 | 范凉凉 | 女 | 45 | 123156789012345678 | 北京 | 2010-04-01 |
| 10 | 00010 | 陈友谅 | 男 | 53 | 123456789012345670 | 上海 | 2011-01-01 |
| 11 | 00011 | 张士诚 | 男 | 55 | 123567897123465670 | 江苏 | 2015-05-01 |
| 12 | 00012 | 常遇奇 | 男 | 32 | 123446757152345678 | 北京 | 2004-02-01 |
| 13 | 00013 | 张三丰 | 男 | 88 | 123656789012345678 | 江苏 | 2020-11-01 |
| 14 | 00014 | 灭绝 | 女 | 65 | 123456719012345670 | 西安 | 2019-05-01 |
| 15 | 00015 | 胡青牛 | 男 | 70 | 12345674971234567X | 西安 | 2018-04-01 |
| 16 | 00016 | 周芷若 | 女 | 18 | null | 北京 | 2012-06-01 |
3.2 数值函数
常见的数值函数如下:
| 函数 | 功能 |
|---|---|
| CEIL(x) | 向上取整 |
| FLOOR(x) | 向下取整 |
| MOD(x,y) | 返回 x 除以 y 的余数(模) |
| RAND() ⭐ | 返回 0~1 范围内的随机数 |
| ROUND(x,y) ⭐ | 对参数 x 进行四舍五入,保留 y 位小数 |
演示如下:
A. ceil:向上取整
select ceil(1.1);B. floor:向下取整
select floor(1.9);C. mod:取模
select mod(7,4);D. rand:获取随机数
select rand();E. round:四舍五入
select round(2.344,2);案例 ⭐⭐⭐
通过数据库的函数,生成一个六位数的随机验证码。
思路:通过 rand() 函数可获取 0~1 之间的随机数,将其乘以 1000000 后,舍弃小数部分,若结果长度不足 6 位,则在前面补 0。
select lpad(round(rand()*1000000 , 0), 6, '0');3.3 日期函数
常见的日期函数如下:
| 函数 | 功能 |
|---|---|
| CURDATE() | 返回当前日期(格式:YYYY-MM-DD) |
| CURTIME() | 返回当前时间(格式:HH:MM:SS) |
| NOW() | 返回当前日期和时间(格式:YYYY-MM-DD HH:MM:SS) |
| YEAR(date) | 获取指定 date 的年份 |
| MONTH(date) | 获取指定 date 的月份 |
| DAY(date) | 获取指定 date 的日期 |
| DATE_ADD(date, INTERVAL expr type) | 返回一个日期 / 时间值,为指定 date 加上一个时间间隔 expr 后的结果 |
| DATEDIFF(date1,date2) | 返回起始时间 date1 和结束时间 date2 之间的天数(date1 - date2) |
演示如下:
A. curdate:当前日期
select curdate();B. curtime:当前时间
select curtime();C. now:当前日期和时间
select now();D. YEAR、MONTH、DAY:获取当前年、月、日
select YEAR(now()); select MONTH(now()); select DAY(now());E. date_add:增加指定的时间间隔
select date_add(now(), INTERVAL 70 YEAR );F. datediff:获取两个日期相差的天数
select datediff('2021-10-01', '2021-12-01');案例
查询所有员工的入职天数,并根据入职天数倒序排序。
思路:入职天数 = 当前日期 - 入职日期,可通过 datediff 函数实现。
select name, datediff(curdate(), entrydate) as 'entrydays' from emp order by entrydays desc;3.4 流程函数
流程函数是一类常用函数,可在 SQL 语句中实现条件筛选,从而提升语句效率。
| 函数 | 功能 |
|---|---|
| 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 默认值 |
演示如下:
A. if
select if(false, 'Ok', 'Error');B. ifnull
select ifnull('Ok','Default'); select ifnull('','Default'); select ifnull(null,'Default');C. case when then else end
需求:查询 emp 表的员工姓名和工作地址(北京 / 上海 → 一线城市,其他 → 二线城市)
select name, ( case workaddress when '北京' then '一线城市' when '上海' then '一线城市' else '二线城市' end ) as '工作地址' from emp;案例
先创建并插入数据到学员成绩表 score:
create table score( id int comment 'ID', name varchar(20) comment '姓名', math int comment '数学', english int comment '英语', chinese int comment '语文' ) comment '学员成绩表'; insert into score(id, name, math, english, chinese) VALUES  (1, 'Tom', 67, 88, 95),  (2, 'Rose' , 23, 66, 90), (3, 'Jack', 56, 98, 76);查询学员成绩,并显示各科成绩等级(≥85 为优秀,≥60 为及格,否则为不及格):
select id,name, (case when math >= 85 then '优秀' when math >=60 then '及格' else '不及格' end ) '数学', (case when english >= 85 then '优秀' when english >=60 then '及格' else '不及格' end ) '英语', (case when chinese >= 85 then '优秀' when chinese >=60 then '及格' else '不及格' end ) '语文' from score;函数场景回顾数据库中存储入职日期(如 2000-01-01),如何快速计算出入职天数?答案:datediff函数。数据库中存储学生分数值(如 98、75),如何快速判定分数等级?答案:case ... when ...流程函数。
4. 约束
4.1 概述
- 概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据。
- 目的:保证数据库中数据的正确、有效性和完整性。
分类:
| 约束 | 描述 | 关键字 |
|---|---|---|
| 非空约束 | 限制该字段的数据不能为 null | NOT NULL |
| 唯一约束 | 保证该字段的所有数据都是唯一、不重复的 | UNIQUE |
| 主键约束⭐ | 主键是一行数据的唯一标识,要求非空且唯一 | PRIMARY KEY |
| 默认约束 | 保存数据时,如果未指定该字段的值,则采用默认值 | DEFAULT |
| 检查约束 | 保证字段值满足某一个条件 | CHECK |
| 外键约束⭐ | 用来让两张表的数据之间建立连接,保证数据的一致性和完整性 | FOREIGN KEY |
注意:约束是作用于表中字段上的,可以在创建表 / 修改表的时候添加约束。
4.2 外键约束
4.2.1 介绍
外键:用来让两张表的数据之间建立连接,从而保证数据的一致性和完整性。
4.2.2 语法
1). 添加外键
-- 方式1:创建表时添加 CREATE TABLE 表名( 字段名 数据类型, [CONSTRAINT] [外键名称] FOREIGN KEY (外键字段名) REFERENCES 主表 (主表列名) ); -- 方式2:修改表时添加 ALTER TABLE 表名 ADD CONSTRAINT [外键名称] FOREIGN KEY (外键字段名) REFERENCES 主表 (主表列名);

多了一个蓝色小钥匙
2). 删除外键
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;案例:删除 emp 表的外键 fk_emp_dept_id
alter table emp drop foreign key fk_emp_dept_id;4.3.3 删除 / 更新
添加外键后,删除 / 更新父表数据时产生的约束行为,称为删除 / 更新行为,具体如下:
| 行为 | 说明 |
|---|---|
| NO ACTION | 当在父表中删除 / 更新对应记录时,先检查该记录是否有对应外键,若有则不允许删除 / 更新(与 RESTRICT 一致),默认行为 |
| RESTRICT | 当在父表中删除 / 更新对应记录时,先检查该记录是否有对应外键,若有则不允许删除 / 更新(与 NO ACTION 一致),默认行为 |
| CASCADE | 当在父表中删除 / 更新对应记录时,先检查该记录是否有对应外键,若有则同步删除 / 更新子表中关联的外键记录 |
| SET NULL | 当在父表中删除对应记录时,先检查该记录是否有对应外键,若有则将子表中该外键值设为 null(需外键允许取 null) |
| SET DEFAULT | 父表有变更时,子表将外键列设为默认值(Innodb 不支持) |
具体语法
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段) REFERENCES 主表名 (主表字段名) ON UPDATE CASCADE ON DELETE CASCADE;演示
NO ACTION 是默认行为,已通过前面语法测试,此处演示 CASCADE 和 SET NULL 两种行为:
1). CASCADE
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on update cascade on delete cascade ;A. 修改父表数据:将 dept 表中 id 为 1 的记录修改为 6。结果:子表 emp 中 dept_id 值为 1 的记录,同步变为 6,体现 CASCADE 级联更新效果。
B. 删除父表数据:删除 dept 表中 id 为 6 的记录。结果:父表数据删除成功,子表中关联的记录也被级联删除,体现 CASCADE 级联删除效果。 注意:一般业务系统中,不会修改表的主键值。
2). SET NULL
先删除已建立的外键 fk_emp_dept_id,再恢复 emp、dept 表数据。
执行以下语句添加外键并设置 SET NULL 行为
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on update set null on delete set null ;5. 多表查询
5.1 多表关系
项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:
- 一对多
- 多对多
- 一对一
5.1.1 一对多
- 案例:部门与员工的关系
- 关系:一个部门对应多个员工,一个员工对应一个部门
- 实现:在多的一方建立外键,指向一的一方的主键

简单SQL演示:
--外键指向主表dept的id alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id)5.1.2 多对多
- 案例:学生与课程的关系
- 关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
- 实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键

具体SQL演示:
--学生表 create table student( id int auto_increment primary key comment '主键ID', name varchar(10) comment '姓名', no varchar(10) comment '学号' ) comment '学生表'; --课程表 create table course( id int auto_increment primary key comment '主键ID', name varchar(10) comment '课程名称' ) comment '课程表'; --学生课程中间表 create table student_course( id int auto_increment comment '主键' primary key, studentid int not null comment '学生ID', courseid int not null comment '课程ID', constraint fk_courseid foreign key (courseid) references course (id), constraint fk_studentid foreign key (studentid) references student (id) )comment '学生课程中间表';5.1.3 一对一
- 案例:用户 与 用户详情的关系
- 关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
- 实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的 (UNIQUE)

具体SQL演示:
--用户基本信息表 create table tb_user( id int auto_increment primary key comment '主键ID', name varchar(10) comment '姓名', age int comment '年龄', gender char(1) comment '1: 男 , 2: 女', phone char(11) comment '手机号' ) comment '用户基本信息表'; --用户教育信息表 create table tb_user_edu( id int auto_increment primary key comment '主键ID', degree varchar(20) comment '学历', major varchar(50) comment '专业', primaryschool varchar(50) comment '小学', middleschool varchar(50) comment '中学', university varchar(50) comment '大学', userid int unique comment '用户ID', constraint fk_userid foreign key (userid) references tb_user(id) ) comment '用户教育信息表';5.2 多表查询概述
5.2.1 概述
多表查询即从多张表中查数据,单表查询 SQL 如select * from emp;,多表查询可先用逗号分隔表,如 select * from emp , dept,但这样会产生 "笛卡尔积"—— 即两张表所有记录的全部组合,需后续加连接条件消除无效组合。

而在多表查询中,我们是需要消除无效的笛卡尔积的,只保留两张表关联部分的数据。
在 SQL 语句中,我们一般通过给多表查询加上连接查询的条件来去除无效的笛卡尔积。
5.2.2 分类
连接查询
内连接:相当于查询 A、B 交集部分数据 外连接:
左外连接:查询左表所有数据,以及两张表交集部分数据
右外连接:查询右表所有数据,以及两张表交集部分数据
自连接:当前表与自身的连接查询,自连接必须使用表别名
子查询
5.3 内连接
内连接查询的是两张表交集部分的数据。(也就是绿色部分的数据)
内连接的语法分为两种:隐式内连接、显式内连接。先来学习一下具体的语法结构。
隐式内连接
SELECT 字段列表 FROM 表1 , 表2 WHERE 条件 ... ;显式内连接
SELECT 字段列表 FROM 表 1 [ INNER ] JOIN 表2 ON 连接条件 ... ;
案例演示:
A. 查询每一个员工的姓名 ,及关联的部门的名称(隐式内连接实现)
表结构:emp , dept 连接条件:emp.dept_id = dept.id
select emp.name , dept.name from emp , dept where emp.dept_id = dept.id ;B. 查询每一个员工的姓名 ,及关联的部门的名称(显式内连接实现)--- INNER JOIN ... ON ...
表结构:emp , dept 连接条件:emp.dept_id = dept.id
select e.name, d.name from emp e inner join dept d on e.dept_id = d.id; -- 为每一张表起别名,简化SQL编写 select e.name, d.name from emp e join dept d on e.dept_id = d.id;表的别名:table a as 别名 1 , table b as 别名 2 table a 别名 1 , table b 别名 2
注意:一旦为表起别名,就不能再用表名来指定对应字段,此时只能使用别名来指定字段
5.4 外连接
外连接分为两种,分别是:左外连接 和 右外连接。具体的语法结构为:
左外连接
SELECT 字段列表 FROM 表 1 LEFT \[ OUTER ] JOIN 表2 ON 条件 ... ;左外连接相当于查询表 1(左表)的所有数据,当然也包含表 1 和表 2 交集部分的数据。
右外连接
SELECT 字段列表 FROM 表 1 RIGHT \[ OUTER ] JOIN 表2 ON 条件 ... ;右外连接相当于查询表 2(右表)的所有数据,当然也包含表 1 和表 2 交集部分的数据。

案例演示:

左外连接和右外连接是可以相互替换的,只需要调整在连接查询时SQL中,表结构的先后顺序就可以了。而我们在日常开发使用时,更偏向于左外连接。
5.5 自连接
5.5.1 自连接查询
自连接查询,顾名思义,就是自己连接自己,也就是把一张表连接查询多次。查询语法:
SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ... ;而对于自连接查询,可以是内连接查询,也可以是外连接查询。
案例演示:

A. 查询员工及其所属领导的名字
表结构:emp
select a.name , b.name from emp a , emp b where a.managerid = b.id;B. 查询所有员工 emp 及其领导的名字 emp , 如果员工没有领导,也需要查询出来
表结构:emp a , emp b
select a.name '员工', b.name '领导' from emp a left join emp b on a.managerid = b.id;注意事项
在自连接查询中,必须要为表起别名,要不然我们不清楚所指定的条件、返回的字段,到底是哪一张表的字段。
5.5.2 联合查询
对于联合查询,就是把多次查询的结果合并起来,形成一个新的查询结果集。
SELECT 字段列表 FROM 表A ... UNION [ ALL ] SELECT 字段列表 FROM 表B ....;对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致。
案例
A. 将薪资低于 5000 的员工,和 年龄大于 50 岁的员工全部查询出来。
当前对于这个需求,我们可以直接使用多条件查询,使用逻辑运算符 or 连接即可。 那这里,我们也可以通过 union/union all 来联合查询。(仅用于演示)
select \* from emp where salary < 5000 union all select \* from emp where age > 50;union all 查询出来的结果,仅仅进行简单的合并,并未去重。
select * from emp where salary < 5000 union select * from emp where age > 50;union 联合查询,会对查询出来的结果进行去重处理。
6 子查询
6.1 概述
1)概念
SQL 语句中嵌套 SELECT 语句,称为嵌套查询,又称子查询。
SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2 );子查询外部的语句可以是 INSERT / UPDATE / DELETE / SELECT 的任何一个。
2)分类
根据子查询结果不同,分为:A. 标量子查询(子查询结果为单个值)B. 列子查询(子查询结果为一列)C. 行子查询(子查询结果为一行)D. 表子查询(子查询结果为多行多列)
根据子查询位置,分为:A. WHERE 之后B. FROM 之后C. SELECT 之后
6.2 标量子查询
子查询返回的结果是单个值(数字、字符串、日期等),这种子查询称为标量 子查询。
常用的操作符:=、<、>、>=、<=
案例
A. 查询 "销售部" 的所有员工信息
完成这个需求时,我们可以将需求分解为两步:
①. 查询 "销售部" 部门 ID
②. 根据 "销售部" 部门 ID, 查询员工信息
select * from emp where dept_id = (select id from dept where name = '销售部');B. 查询在 "方东白" 入职之后的员工信息
完成这个需求时,我们可以将需求分解为两步:
①. 查询 方东白 的入职日期
②. 查询指定入职日期之后入职的员工信息
select * from emp where entrydate > (select entrydate from emp where name = '方东白');6.3 列子查询
子查询返回的结果是一列(可以是多行),这种子查询称为列 子查询。
常用的操作符:IN 、NOT IN 、 ANY 、SOME 、 ALL
| 操作符 | 描述 |
|---|---|
| IN | 在指定的集合范围之内,多选一 |
| NOT IN | 不在指定的集合范围之内 |
| ANY | 子查询返回列表中,有任意一个满足即可 |
| SOME | 与 ANY 等同,使用 SOME 的地方都可以使用 ANY |
| ALL | 子查询返回列表的所有值都必须满足 |
案例
A. 查询 "销售部" 和 "市场部" 的所有员工信息
分解为以下两步:
①. 查询 "销售部" 和 "市场部" 的部门 ID
②. 根据部门 ID, 查询员工信息
select * from emp where dept_id in (select id from dept where name = '销售部' or name = '市场部');
6.4 行子查询
子查询返回的结果是一行(可以是多列),这种子查询称为行 子查询。
常用的操作符:= 、<> 、IN 、NOT IN
案例
A. 查询与 "张无忌" 的薪资及直属领导相同的员工信息;
这个需求同样可以拆解为两步进行:
①. 查询 "张无忌" 的薪资及直属领导
②. 查询与 "张无忌" 的薪资及直属领导相同的员工信息;
select * from emp where (salary,managerid) = (select salary, managerid from emp where name = '张无忌');6.5 表子查询
子查询返回的结果是多行多列,这种子查询称为表 子查询。
常用的操作符:IN
案例
A. 查询与 "鹿杖客" , "宋远桥" 的职位和薪资相同的员工信息
分解为两步执行:
①. 查询 "鹿杖客" , "宋远桥" 的职位和薪资
②. 查询与 "鹿杖客" , "宋远桥" 的职位和薪资相同的员工信息
select * from emp where (job,salary) in ( select job, salary from emp where name = '鹿杖客' or name = '宋远桥' );7. 存储引擎
7.1 MySQL体系结构

1. 连接层主要处理客户端的连接请求,包括本地 socket 通信和基于 TCP/IP 的网络通信。负责客户端的授权认证,确保只有合法用户可以接入。提供基于 SSL/TLS 的加密通信,保障数据传输的安全性。引入线程池机制,为通过认证的客户端分配线程资源,提升并发处理能力。验证客户端的操作权限,确保用户只能执行授权范围内的操作。
2.服务层解析 SQL 查询并创建内部解析树,进行优化。实现跨存储引擎的功能,如存储过程和函数的调用。对于SELECT查询,优先查询内部缓存,以提高系统在高并发读操作环境下的性能。负责处理 SQL 查询的核心逻辑,包括提供 SQL 接口、缓存查询、SQL 分析与优化等。
3.引擎层数据库索引的实现位于该层。支持多种存储引擎,用户可以根据需求选择合适的引擎。负责 MySQL 数据的存储和提取,通过 API 与服务器通信。
4). 存储层
主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
(如: Redo,Undo,Data,Index,Error,Binary,Query and Slow等)
7.2 存储引擎介绍
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果没有指定将自动选择默认的存储引擎(InnoDB)。
1). 建表时指定存储引擎
CREATE TABLE 表名( 字段1 字段1类型 [ COMMENT 字段1注释 ] , ...... 字段n 字段n类型 [COMMENT 字段n注释 ] ) ENGINE = INNODB [ COMMENT 表注释 ] ;2). 查询当前数据库支持的存储引擎
show engines;
7.3 存储引擎特点
7.3.1 InnoDB

逻辑存储结构

7.3.2 MyISAM

7.3.3 Memory

三种引擎的区别

面试题: InnoDB引擎与MyISAM引擎的区别?
①. InnoDB引擎, 支持事务, 而MyISAM不支持。
②. InnoDB引擎, 支持外键, 而MyISAM是不支持的。
③. InnoDB引擎, 支持行锁和表锁, 而MyISAM仅支持表锁, 不支持行锁。
7.4 存储引擎选择
在选择存储引擎时,应根据应用系统的具体需求来决定。对于复杂的应用,可以根据需求组合使用多种存储引擎。
- InnoDB:作为 MySQL 的默认存储引擎,它支持事务处理和外键约束。适用于需要高事务完整性和在高并发环境下保持数据一致性的应用。如果应用中包含大量的更新和删除操作,InnoDB 是一个理想的选择。
- MyISAM:适用于以读取和插入操作为主,且更新和删除操作较少的场景。如果应用对事务完整性和并发处理的要求不高,MyISAM 是一个合适的选择。(MangoDB替代)
- MEMORY:将数据存储在内存中,提供快速访问,适合用作临时表和缓存。但MEMORY 存储引擎对表的大小有限制,且在系统故障时很难保证数据的持久性。(Redis替代)
8. 索引
8.1 索引概述
8.1.1 介绍

8.1.2 演示

8.1.3 特点

8.2 索引结构
8.2.1 概述
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:

不同的存储引擎对于索引结构的支持情况:

8.2.2 二叉树
假如说MySQL的索引结构采用二叉树的数据结构:

8.2.3 B-Tree


8.2.4 B+Tree


上述我们所看到的结构是标准的B+Tree,我们再来看看MySQL中优化之后的 B+Tree。

8.2.5 Hash


8.2.6 总结

8.3 索引分类
8.3.1 索引分类
在MySQL数据库,将索引的具体类型主要分为主键索引、唯一索引、常规索引、全文索引。

8.3.2 聚集索引&二级索引


- 聚集索引的叶子节点下挂的是这一行的数据 。
- 二级索引的叶子节点下挂的是该字段值对应的主键值。
接下来,我们来分析一下,当我们执行如下的SQL语句时,具体的查找过程是什么样子的。

具体过程如下:
①. 由于是根据name字段进行查询,所以先根据name='Arm'到name字段的二级索引中进行匹配查 找。但是在二级索引中只能查找到 Arm 对应的主键值 10。
②. 由于查询返回的数据是*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最 终找到10对应的行row。
③. 最终拿到这一行的数据,直接返回即可。
回表查询: 这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取 数据的方式,就称之为回表查询。
8.3.3 思考
思考题: 以下两条SQL语句,那个执行效率高? 为什么?
A. select * from user where id = 10 ;
B. select * from user where name = 'Arm' ;
解答: A 语句的执行性能要高于B 语句。 因为A语句直接走聚集索引,直接返回数据。 而B语句需要先查询name字段的二级索引,然 后再查询聚集索引,也就是需要进行回表查询。
8.4 索引语法



8.5 SQL性能分析
8.5.1 SQL执行频率


Com_delete: 删除次数
Com_insert: 插入次数
Com_select: 查询次数
Com_update: 更新次数
通过上述指令,我们可以查看到当前数据库到底是以查询为主,还是以增删改为主,从而为数据 库优化提供参考依据。 如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以 查询为主,那么就要考虑对数据库的索引进行优化了。
8.5.2 慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有 SQL语句的日志。 MySQL的慢查询日志默认未开启,我们可以查看系统变量 slow_query_log。

如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
# 开启MySQL慢日志查询开关1 SET GLOBAL slow_query_log = 'ON'; # 开启MySQL慢日志查询开关2 slow_query_log=1 # 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志 long_query_time=2 配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息 /var/lib/mysql/localhost-slow.log。
systemctl restart mysqld8.5.3 profile详情
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling 参数,能够看到当前MySQL是否支持profile操作:
SELECT @@have_profiling ;可以看到,当前MySQL是支持 profile操作的,但是开关是关闭的。可以通过set语句在 session/global级别开启profiling:
SET profiling = 1;开关已经打开了,接下来,我们所执行的SQL语句,都会被MySQL记录,并记录执行时间消耗到哪儿去 了。 我们直接执行如下的SQL语句:
select * from tb_user; select * from tb_user where id = 1; select * from tb_user where name = '白起'; select count(*) from tb_sku;执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:
-- 查看每一条SQL的耗时基本情况 show profiles; -- 查看指定query_id的SQL语句各个阶段的耗时情况 show profile for query query_id; -- 查看指定query_id的SQL语句CPU的使用情况 show profile cpu for query query_id; 

8.5.4 explain




8.6 索引使用
8.6.1 验证索引效率
在讲解索引的使用原则之前,先通过一个简单的案例,来验证一下索引,看看是否能够通过索引来提升 数据查询性能。在演示的时候,我们还是使用之前准备的一张表 tb_sku , 在这张表中准备了1000w 的记录。

经过试验,字段建立了索引之后,查询性能大大提升。建立索引前后,查询耗时不是同一数量级的。
8.6.2 最左前缀法则

最左边的类必须查询,且中间不跳过索引
8.6.3 范围查询


8.6.4 索引失效情况
1. 索引列运算
不要在索引列上进行运算操作, 索引将失效。

2. 字符串不加引号
字符串类型字段使用时,不加引号,索引将失效。

3. 模糊查询
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。

4. or连接条件
用or分割开的条件, 如果or前条件中的列有索引,而后面列中没有索引,那么涉及的索引都不会 被用到。

5. 数据分布影响
如果MySQL评估使用索引比全表更慢,则不使用索引。

8.6.5 SQL提示

8.6.6 覆盖索引
1.介绍

2.不同SQL语句查询流程

思考题:
一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对 以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from tb_user where username = 'itcast';
答案:
针对于 username, password建立联合索引
sql为: create index idx_user_name_pass on tb_user(username,password);
8.6.7 前缀索引
当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让 索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
1). 语法
create index idx_xxxx on table_name(column(n)) ;2). 前缀长度
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值, 索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
select count(distinct email) / count(*) from tb_user ; select count(distinct substring(email,1,10)) / count(*) from tb_user ; select count(distinct substring(email,1,5)) / count(*) from tb_user ;3). 前缀索引的查询流程

8.6.8 单列索引与联合索引
单列索引:即一个索引只包含单个列
联合索引:即一个索引包含了多个列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引, 而非单列索引

联合索引查询B+树的结构

8.7 索引设计原则
针对于数据量较大,且查询比较频繁的表建立索引。针对于常作为查询条件、排序、分组操作的字段建立索 引。尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。(name,phone)如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间, 避免回表,提高查询效率。要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含 NULL值时,它可以更好地确定哪个索引最有效地用于查询。
8.8 索引总结


9. SQL优化
9.1 插入数据
9.1.1 insert
如果我们需要一次性往数据库表中插入多条记录,可以从以下三个方面进行优化。
1. 批量插入数据
Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry')2. 手动控制事务
start transaction; insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry'); insert into tb_test values(4,'Tom'),(5,'Cat'),(6,'Jerry'); insert into tb_test values(7,'Tom'),(8,'Cat'),(9,'Jerry'); commit;3. 主键顺序插入,性能要高于乱序插入
主键乱序插入 : 8 1 9 21 88 2 4 15 89 5 7 3 主键顺序插入 : 1 2 3 4 5 7 8 9 15 21 88 899.1.2 大批量插入数据
如果一次性需要插入大批量数据(比如: 几百万的记录),使用insert语句插入性能较低,此时可以使 用MySQL数据库提供的load指令进行插入。操作如下:

-- 客户端连接服务端时,加上参数 -–local-infile mysql –-local-infile -u root -p-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关 set global local_infile = 1;-- 执行load指令将准备好的数据,加载到表结构中 load data local infile '/root/sql1.log' into table tb_user fields terminated by ',' lines terminated by '\n' ; Linux执行大批量插入数据:

#如果数据较少,可直接执行.sql文件 source 文件路径/文件名.sql;9.2 主键优化
1). 数据组织方式

2). 页分裂


3). 页合并

4). 索引设计原则

9.3 order by优化

案例:

解决办法:


尽量使用覆盖索引根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则多字段排序, 一个升序一个降序,需要注意联合索引在创建时的规则(ASC/DESC)如果不可避免的出现filesort,大数据量排序时,可增大排序缓冲区大小 sort_buffer_size
9.4 group by优化



所以,在分组操作中,我们需要通过以下两点进行优化,以提升性能:
A. 在分组操作时,可以通过索引来提高效率。
B. 分组操作时,索引的使用也是满足最左前缀法则的。
9.4 limit优化
优化思路: 一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引加子查 询形式进行优化。
explain select * from tb_sku t , (select id from tb_sku order by id limit 2000000,10) a where t.id = a.id;9.5 count优化

几种count计数比较

9.6 update优化

本文涵盖视图、存储过程、存储函数、触发器、锁机制、InnoDB引擎及MySQL管理工具。视图可简化操作并提高安全性存储过程和函数能实现复杂逻辑与重复利用触发器用于记录操作日志锁机制保障并发数据一致性InnoDB引擎支持事务与MVCC实现高效并发MySQL管理工具助力数据库的日常管理和维护
10. 视图/存储过程/触发器
10.1 视图
在MySQL中,视图(View)是一种虚拟表,其内容由SQL查询定义。视图并不实际存储数据,而是基于一个或多个基础表(实际的物理表)的查询结果动态生成数据。
10.1.1 介绍
视图是基于SQL语句的结果集的可视化表示。它类似于一个表,但并不实际存储数据。视图的结构和数据是通过定义它的查询语句来确定的。
10.1.2 语法
1). 创建
#select语句执行的时私用的表就是当前创建视图的基表 CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]2). 查询
查看创建视图语句:SHOW CREATE VIEW 视图名称; 查看视图数据:SELECT * FROM 视图名称 ...... ;3). 修改
方式一:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ] 方式二:ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]4). 删除
DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ...我们来测试能不能通过视图来插入、更新数据
create or replace view stu_v_1 as select id,name from student where id <= 10 ; select * from stu_v_1; insert into stu_v_1 values(6,'Tom'); insert into stu_v_1 values(17,'Tom22');基表 视图


然而,视图中并没有相关的id为6的数据(由于视图中查询语句限制 id<=10)
因为我们在创建视图的时候,指定的条件为 id<=10, id为17的数据,是不符合条件的,所以没有查 询出来,但是这条数据确实是已经成功的插入到了基表中。
如果我们定义视图时,如果指定了条件,然后我们在插入、修改、删除数据时,是否可以做到必须满足条件才能操作,否则不能够操作呢? 答案是可以的,这就需要借助于视图的检查选项了。
10.1.3 检查选项
当使用WITH CHECK OPTION子句创建视图时,MySQL会在通过视图进行插入、更新或删除操作时,检查每一行是否符合视图的定义。此外,如果视图是基于另一个视图创建的,MySQL还会检查依赖视图中的规则,以确保数据的一致性。为了确定检查的范围,MySQL提供了两个选项:CASCADED(级联)和LOCAL,默认值为CASCADED。
- CASCADED(级联):如果视图(如
v2)是基于另一个视图(如v1)创建的,并且在v2视图创建时指定了WITH CHECK OPTION CASCADED,那么在执行数据修改操作时,MySQL不仅会检查v2视图的规则,还会级联检查v1视图的规则,即使v1视图在创建时未指定WITHCHECK OPTION。 - LOCAL(本地):如果视图(如
v2)是基于另一个视图(如v1)创建的,并且在v2视图创建时指定了WITH CHECK OPTION LOCAL,那么在执行数据修改操作时,MySQL只会检查v2视图本身的规则,而不会检查其依赖的v1视图的规则,即使v1视图在创建时未指定WITH CHECK OPTION。


10.1.4 视图的更新

10.1.5 视图作用
1). 简单
视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视 图,从而使得用户不必为以后的操作每次指定全部的条件。
2). 安全
数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见 到的数据
3). 数据独立
视图可帮助用户屏蔽真实表结构变化带来的影响
10.1.6 案例

create view tb_user_view as select id,name,profession,age,gender,status,createtime from tb_user; select * from tb_user_view; create view tb_stu_course_view as select s.name student_name , s.no student_no , c.name course_name from student s, student_course sc , course c where s.id = sc.studentid and sc.courseid = c.id; select * from tb_stu_course_view; 11.2 存储过程
11.2.1 介绍


11.2.2 基本语法
1). 创建
CREATE PROCEDURE 存储过程名称 ([ 参数列表 ]) BEGIN-- SQL语句 END ;2). 调用
CALL 名称 ([ 参数 ]); 3). 查看
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询指 定数据库的存储过程及状态信息 SHOW CREATE PROCEDURE 存储过程名称 ; -- 查询某个存储过程的定义4). 删除
DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ;

在命令行中,执行创建存储过程的SQL时,需通过关键字delimiter指定SQL语句的结束符。
11.2.3 变量
在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量。
11.2.3.1 系统变量
系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)
1). 查看系统变量
SHOW [ SESSION | GLOBAL ] VARIABLES ; -- 查看所有系统变量 SHOW [ SESSION | GLOBAL ] VARIABLES LIKE '......'; -- 可以通过LIKE模糊匹配方 式查找变量 SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值2). 设置系统变量
SET [ SESSION | GLOBAL ] 系统变量名 = 值 ; SET @@[SESSION | GLOBAL]系统变量名 = 值 ; 
如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
mysql服务重启之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。
A. 全局变量(GLOBAL): 全局变量针对于所有的会话。
B. 会话变量(SESSION): 会话变量针对于单个会话,在另外一个会话窗口就不生效了。
11.2.3.2 用户定义变量
用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 "@变量 名" 使用就可以。其作用域为当前连接。
1). 赋值
#方式一 SET @var_name = expr [, @var_name = expr] ... ; SET @var_name := expr [, @var_name := expr] ... ; #赋值时,可以使用 = ,也可以使用 := #方式二 SELECT @var_name := expr [, @var_name := expr] ... ; SELECT 字段名 INTO @var_name FROM 表名;
注意: 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
11.2.3.3 局部变量
局部变量是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的 局部变量和输入参数,局部变量的范围是在其内声明的BEGIN ... END块。
1). 声明
DECLARE 变量名 变量类型 [DEFAULT ... ] ;变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。
2). 赋值
SET 变量名 = 值 ; SET 变量名 := 值 ; SELECT 字段名 INTO 变量名 FROM 表名 ... ; 演示示例:
#声明局部变量 - declare-- 赋值 create procedure p2() begin declare stu_count int default 0; select count(*) into stu_count from student; select stu_count; end; call p2();11.2.4 if
1). 介绍
if 用于做条件判断,具体的语法结构为:
IF 条件1 THEN ..... ELSEIF 条件2 THEN -- 可选 ..... ELSE -- 可选 ..... END IF在if条件判断结构中,ELSE IF 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有。
案例:

11.2.5 参数
1). 介绍
参数的类型,主要分为以下三种:IN、OUT、INOUT。 具体的含义如下:

2). 用法
CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ]) BEGIN -- SQL语句 END ;3). 练习

4). 代码实现
create procedure p4(in score int, out result varchar(10)) begin if score >= 85 then set result := '优秀'; elseif score >= 60 then set result := '及格'; else set result := '不及格'; end if; end; -- 定义用户变量 @result来接收返回的数据, 用户变量可以不用声明 call p4(18, @result); select @resultcreate procedure p5(inout score double) begin set score := score * 0.5; end; set @score = 198; call p5(@score); select @score;11.2.6 case
1). 介绍
case结构及作用,和我们在基础篇中所讲解的流程控制函数很类似。有两种语法格式:
语法1:
-- 含义: 当case_value的值为 when_value1时,执行statement_list1,当值为 when_value2时, 执行statement_list2, 否则就执行 statement_list CASE case_value WHEN when_value1 THEN statement_list1 [ WHEN when_value2 THEN statement_list2] ... [ ELSE statement_list ] END CASE;语法2:
-- 含义: 当条件search_condition1成立时,执行statement_list1,当条件search_condition2成 立时,执行statement_list2, 否则就执行 statement_list CASE WHEN search_condition1 THEN statement_list1 [WHEN search_condition2 THEN statement_list2] ... [ELSE statement_list] END CASE;案例:

create procedure p6(in month int) begin declare result varchar(10); case when month >= 1 and month <= 3 then set result := '第一季度'; when month >= 4 and month <= 6 then set result := '第二季度'; when month >= 7 and month <= 9 then set result := '第三季度'; when month >= 10 and month <= 12 then set result := '第四季度'; else set result := '非法参数'; end case ; select concat('您输入的月份为: ',month, ', 所属的季度为: ',result); end; call p6(16);11.2.7 while
1). 介绍
while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。具体语法为:
-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑 WHILE 条件 DO SQL逻辑... END WHILE;2). 案例
计算从1累加到n的值,n为传入的参数值。
create procedure p1(in n int) begin declare total int default 0; while n>0 do set total := total + n; set n := n - 1; end while; select total; end; call p1(100);11.2.8 repeat
1). 介绍
repeat是有条件的循环控制语句, 当满足until声明的条件的时候,则退出循环 。具体语法为:
-- 先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环 REPEAT SQL逻辑... UNTIL 条件 END REPEAT;2). 案例
计算从1累加到n的值,n为传入的参数值。(使用repeat实现)
-- A. 定义局部变量, 记录累加之后的值;-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 create procedure p8(in n int) begin declare total int default 0; repeat #后面没有其他语句 set total := total + n; set n := n - 1; until n <= 0 #后面没有分号 end repeat; select total; end; call p8(10); call p8(100);11.2.9 loop
1). 介绍
LOOP 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。 LOOP可以配合一下两个语句使用: LEAVE :配合循环使用,退出循环。 ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
[begin_label:] LOOP SQL逻辑... END LOOP [end_label];LEAVE label; -- 退出指定标记的循环体 ITERATE label; -- 直接进入下一次循环上述语法中出现的 begin_label,end_label,label 指的都是我们所自定义的标记。
2). 案例

A. 定义局部变量, 记录累加之后的值;-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx create procedure p9(in n int) begin declare total int default 0; sum:loop if n<=0 then leave sum; end if; set total := total + n; set n := n - 1; end loop sum; select total; end; call p9(100);-- A. 定义局部变量, 记录累加之后的值;-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx-- C. 如果当次累加的数据是奇数, 则直接进入下一次循环. --------> iterate xx create procedure p10(in n int) begin declare total int default 0; #自定义循环名称(sum) sum:loop if n<=0 then leave sum; end if; if n%2 = 1 then end if; set n := n - 1; iterate sum; set total := total + n; set n := n - 1; end loop sum; select total; end; call p10(100);11.2.10 游标
1). 介绍
游标(CURSOR)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进 行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下。
A. 声明游标
DECLARE 游标名称 CURSOR FOR 查询语句 ;B. 打开游标
OPEN 游标名称 ;C. 获取游标记录
FETCH 游标名称 INTO 变量 [, 变量 ] ;D. 关闭游标
CLOSE 游标名称 ;2). 案例
根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名 (name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表 (id,name,profession)中。
-- 逻辑: -- A. 声明游标, 存储查询结果集 -- B. 准备: 创建表结构 -- C. 开启游标 -- D. 获取游标中的记录 -- E. 插入数据到新表中 -- F. 关闭游标 create procedure p11(in uage int) begin declare uname varchar(100); declare upro varchar(100); -- A declare u_cursor cursor for select name,profession from tb_user where age <= uage; drop table if exists tb_user_pro; -- B create table if not exists tb_user_pro( id int primary key auto_increment, name varchar(100), profession varchar(100) ); -- C open u_cursor; while true do -- D fetch u_cursor into uname,upro; -- E insert into tb_user_pro values (null, uname, upro); end while; -- F close u_cursor; end; call p11(30); 

上述的功能,虽然我们实现了,但是逻辑并不完善,而且程序执行完毕,获取不到数据,数据库还报 错。接下来,我们就需要来完成这个存储过程,并且解决这个问题。
要想解决这个问题,就需要通过MySQL中提供的 条件处理程序 Handler 来解决。
11.2.11 条件处理程序
1). 介绍
条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为:
DECLARE ... handler_action statement ; handler_action 的取值: HANDLER FOR CONTINUE: 继续执行当前程序 EXIT: 终止执行当前程序 condition_value 的取值: condition_value [, condition_value] SQLSTATE sqlstate_value: 状态码,如 02000 SQLWARNING: 所有以01开头的SQLSTATE代码的简写 NOT FOUND: 所有以02开头的SQLSTATE代码的简写 SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写A. 通过SQLSTATE指定具体的状态码
-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出 declare exit handler for SQLSTATE '02000' close u_cursor;B. 通过SQLSTATE的代码简写方式 NOT FOUND
-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02开头时,将关闭游标u_cursor,并退出 declare exit handler for not found close u_cursor;11.3 存储函数
1). 介绍
存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。具体语法如下:
CREATE FUNCTION 存储函数名称 ([ 参数列表 ]) RETURNS type [characteristic ...] BEGIN-- SQL语句 RETURN ...; END ;characteristic说明:NO SQL :不包含 SQL 语句。DETERMINISTIC:相同的输入参数总是产生相同的结果。READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句。
2). 案例
计算从1累加到n的值,n为传入的参数值。
create function fun1(n int) returns int deterministic begin declare total int default 0; while n>0 do set total := total + n; set n := n - 1; end while; return total; end; select fun1(50);11.4 触发器
11.4.1 介绍

触发器与AOP的相似点关注点分离MySQL触发器:触发器允许开发者在数据库层面定义一些自动执行的操作,这些操作与主业务逻辑分离。例如,当表中的数据被插入、更新或删除时,触发器可以自动执行一些额外的逻辑,如记录日志、更新关联表等。这样,触发器将这些辅助逻辑从业务逻辑中分离出来,使得主业务逻辑更加清晰。面向切面编程(AOP):AOP的核心思想是将横切关注点(如日志记录、事务管理、权限校验等)从业务逻辑中分离出来。通过定义切面(Aspect),这些横切关注点可以独立于主业务逻辑存在,从而提高代码的可维护性和可扩展性。共同点:两者都实现了关注点的分离,将一些通用的、辅助性的逻辑从业务逻辑中分离出来,避免了代码的重复和混乱,使得主逻辑更加清晰和简洁。自动执行MySQL触发器:触发器是自动触发的。当满足特定的数据库操作(如INSERT、UPDATE、DELETE)时,触发器会自动执行预定义的SQL语句或逻辑,无需在应用程序代码中显式调用。面向切面编程(AOP):AOP框架(如Spring AOP)会自动拦截方法调用,并在合适的位置(如方法执行前、执行后、抛出异常时等)插入切面逻辑。开发者只需要定义好切面和切入点,框架会自动处理切面逻辑的执行。共同点:两者都具有自动执行的特性,无需在主逻辑中显式调用辅助逻辑,减少了开发者的负担,提高了代码的可维护性。增强功能MySQL触发器:通过触发器,可以在数据库层面增强数据的完整性和一致性。例如,可以在插入或更新数据时自动校验数据的有效性,或者在删除数据时自动清理相关联的数据。面向切面编程(AOP):通过切面,可以在不修改主业务逻辑代码的情况下,为系统添加额外的功能,如日志记录、性能监控、事务管理等。共同点:两者都可以在不直接修改主逻辑代码的情况下,为系统添加额外的功能,增强了系统的整体能力。
11.4.2 语法
1). 创建
CREATE TRIGGER trigger_name BEFORE/AFTER INSERT/UPDATE/DELETE ON tbl_name FOR EACH ROW -- 行级触发器 BEGIN trigger_stmt ; END;2). 查看
SHOW TRIGGERS ;3). 删除
DROP TRIGGER [schema_name.]trigger_name ; --如果没有指定 schema_name,默认当前数据库 4). 案例

create table user_logs( id int(11) not null auto_increment, operation varchar(20) not null comment '操作类型, insert/update/delete', operate_time datetime not null comment '操作时间', operate_id int(11) not null comment '操作的ID', operate_params varchar(500) comment '操作参数', primary key(`id`) )engine=innodb default charset=utf8; --记录插入操作的日志,故而仅仅记录插入的操作信息 create trigger carbon_insert_trigger after insert on carbon_emission for each row begin insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES (null,'insert',now(),new.id,concat('插入的数据内容为:id=',NEW.id,'name=',new.name,',description=',new.description)); end; show triggers ; --记录修改操作的日志,故而仅仅记录修改前后的操作信息 create trigger tb_user_update_trigger after update on tb_user for each row begin insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES (null, 'update', now(), new.id, concat('更新之前的数据:,name=',old.name, ', phone=', old.phone, ', email=', old.email, ', profession=', old.profession, ' | 更新之后的数据:,name=',new.name, ', phone=', NEW.phone, ', email=', NEW.email, ', profession=', NEW.profession)); end; --记录删除操作的日志,故而仅仅记录删除的操作信息 create trigger tb_user_delete_trigger after delete on tb_user for each row begin insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES (null, 'delete', now(), old.id, concat('删除之前的数据:,name=',old.name, ', phone=', old.phone, ', email=', old.email, ', profession=', old.profession)); end; 12. 锁
12.1 概述
在计算机系统中,锁是一种用于协调多个进程或线程并发访问共享资源的机制。数据库作为一种供众多用户共享的数据资源,其并发访问的一致性和有效性保障是数据库设计与运行的关键问题之一。锁冲突更是直接影响数据库并发访问性能的重要因素,因此,锁机制在数据库中显得尤为重要且复杂。
MySQL数据库中的锁,按照锁的粒度划分,主要有以下三类:
- 全局锁:锁定数据库中的所有表,对整个数据库实例进行加锁,适用于全库范围内的维护操作或数据备份等场景。
- 表级锁:每次操作锁定整张表,适用于对整表进行批量操作或维护时,但并发性能相对较低,因为同一时间只能有一个操作对表进行写入。
- 行级锁:每次操作仅锁定对应的行数据,能够实现更细粒度的并发控制,允许多个事务同时对表中的不同行进行操作,从而提高并发性能,但行级锁的管理和维护成本相对较高。
不同粒度的锁适用于不同的操作场景,合理选择锁的类型对于提升数据库性能和保障数据一致性至关重要。
12.2 全局锁
12.2.1 介绍
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。 其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。为什么全库逻辑备份,就需要加全就锁呢?
A. 我们一起先来分析一下不加全局锁,可能存在的问题。

在进行数据备份时,如果按照以下顺序操作,可能会导致备份数据不一致的问题:首先备份了tb_stock库存表。然后在业务系统中执行了下单操作,扣减库存并生成订单(更新tb_stock表,插入tb_order表)。接着备份tb_order表。执行插入订单日志操作。最后备份tb_orderlog表。
此时备份的数据存在一致性问题:tb_stock表与tb_order表的数据不匹配(tb_order表中有最新的订单信息,但tb_stock表的库存尚未扣减)。
为了避免这种问题,可以借助 MySQL 的 全局锁 来解决。通过在备份前对整个数据库加全局锁,确保在备份期间所有表的数据状态保持一致,从而避免因并发操作导致的数据不一致问题。
B. 再来分析一下加了全局锁后的情况

对数据库进行进行逻辑备份之前,先对整个数据库加上全局锁,一旦加了全局锁之后,其他的DDL、 DML全部都处于阻塞状态,但是可以执行DQL语句,也就是处于只读状态,而数据备份就是查询操作。 那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的一致性 和完整性。
12.2.2 语法
1). 加全局锁
#全局锁(只读锁) flush tables with read lock ;2). 数据备份
mysqldump -uroot –p1234 itcast > itcast.sql3). 释放锁
unlock tables ;4). 演示:

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

如果一个事务对表加了共享读锁,其他事务(包括当前事务自己)对该表的写操作(如INSERT、UPDATE、DELETE)会被阻塞,直到共享读锁被释放。
自己的写操作也会被锁住在大多数数据库系统中,表共享读锁会阻止当前事务自己对该表的写操作。如果当前事务已经对表加了共享读锁,它自己也无法对该表进行写操作,除非先释放共享读锁。
B. 表独占-写锁(write lock)

阻塞其他操作阻止读操作:其他事务对该表的读操作(如SELECT)会被阻塞,直到独占写锁被释放。阻止写操作:其他事务对该表的写操作(如INSERT、UPDATE、DELETE)也会被阻塞,直到独占写锁被释放。
12.3.3 元数据锁
meta data lock , 元数据锁,简写MDL。 MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维 护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。
核心作用:为了避免DML与 DDL冲突,保证读写的正确性。
这里的元数据,大家可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务 时,是不能够修改这张表的表结构的。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变 更操作的时候,加MDL写锁(排他)。 常见的SQL操作时,所添加的元数据锁:

SHARE_READ和SHARE_WRITE兼容,两者与EXLCLUSIVE互斥

我们可以通过下面的SQL,来查看数据库中的元数据锁的情况:
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;12.3.4 意向锁
1). 介绍
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
有了意向锁之后前:
客户端一,开启一个事务,然后执行DML操作,在执行DML语句时,会对涉及到的行加行锁。
当客户端二,想对这张表加表锁时,会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就 会从第一行数据,检查到最后一行数据,效率较低。

有了意向锁之后 :
客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。

而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而不用逐行判断行锁情况了。

2). 分类
- 意向共享锁(IS): 由语句select ... lock in share mode添加 。
- 与表锁共享锁 (read)兼容,与表锁排他锁(write)互斥。
- 意向排他锁(IX): 由insert、update、delete、select...for update添加 。
- 与表锁共 享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。
一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;12.4 行级锁
12.4.1 介绍
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在 InnoDB存储引擎中。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的 锁。对于行级锁,主要分为以下三类:
- 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在 RC、RR隔离级别下都支持。

- 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事 务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。

- 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。 在RR隔离级别下支持。

12.4.2 行锁
1). 介绍
InnoDB实现了以下两种类型的行锁:
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
- 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
两种行锁的兼容情况如下:

常见的SQL语句,在执行时,所加的行锁如下:

2). 演示
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜 索和索引扫描,以防止幻读。
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
- InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;无索引行锁升级为表锁

12.4.3 间隙锁&临键锁
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜 索和索引扫描,以防止幻读。
- 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
- 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。
- 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁

注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会 阻止另一个事务在同一间隙上采用间隙锁。
13. InnoDB引擎
13.1 逻辑存储结构
InnoDB的逻辑存储结构如下图所示:


Tablespace(表空间):表空间是InnoDB中最大的存储单位,用于存储数据库对象(如表、索引等)。表空间可以分为系统表空间、文件表空间、通用表空间和临时表空间等。系统表空间包含InnoDB的系统数据,如数据字典和撤销日志。图中显示了表空间包含多个段(Segment)。Segment(段):段是表空间中的一个逻辑存储单元,用于存储特定类型的数据。每个表在InnoDB中通常有三个段:数据段、索引段和回滚段。数据段用于存储表的数据,索引段用于存储索引,回滚段用于存储回滚信息。图中显示了表空间包含多个段,每个段包含多个区(Extent)。Extent(区):区是段中的一个存储单元,由多个连续的页(Page)组成。InnoDB中的区大小通常是1MB(由64个16KB的页组成)。区是InnoDB分配和管理空间的基本单位。图中显示了每个段包含多个区,每个区包含多个页。Page(页):页是InnoDB中最小的存储单元,通常大小为16KB。页是InnoDB进行I/O操作的基本单位。不同类型的页用于存储不同类型的数据,如数据页、索引页、undo页等。图中显示了每个区包含多个页,每个页用于存储行数据。Row(行):行是数据库表中的一行数据,包含多个列(Column)。每个行数据存储在页中,页中可以存储多行数据。图中显示了页中包含多行数据,每行数据包含事务ID(Trx id)、回滚指针(Roll pointer)和列数据(col1, col2, col3等)。
13.2 架构
13.2.1 概述
MySQL5.5 版本开始,默认使用InnoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发 中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。

13.2.2 内存结构
1.Buffer Pool:

2.ChangBuffer:

3.Adaptive Hash Index:

4.Log Buffer:

13.2.3 磁盘结构
1.System Tablespace & File-Per-Table Tablespaces

2.General Tablespaces & Undo Tabelspaces & Temporary Tablespaces

3.Doublewrite Buffer Files & Redo Log
13.2.4 后台线程

13.3 事务原理
13.3.1 事务基础
1). 事务
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系 统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
2). 特性
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
那实际上,我们研究事务原理,就是研究MySQL的InnoDB引擎是如何保证事务的这四大特性的。

13.3.2 redo log

有了redolog之后,当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在redo log buffer中。在事务提交时,会将redo log buffer中的数据刷新到redo log磁盘文件中。 过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于redo log进行数据恢复,这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘或者涉及到的数据已经落盘,此 时redolog就没有作用了,就可以删除了,所以存在的两个redolog文件是循环写的。
那为什么每一次提交事务,要刷新redo log 到磁盘中呢,而不是直接将buffer pool中的脏页刷新到磁盘呢 ?
因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而redo log在 往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的(追加写入)。顺序写的效率,要远大于随机写。 这 种先写日志的方式,称之为 WAL(Write-Ahead Logging)。
13.3.3 undo log

13.4 MVCC
13.4.1 基本概念
1). 当前读
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加 锁。对于我们日常的操作,如:select ... lock in share mode(共享锁),select ... for update、update、insert、delete(排他锁)都是一种当前读。

在测试中我们可以看到,即使是在默认的RR隔离级别下,事务A中依然可以读取到事务B最新提交的内 容,因为在查询语句后面加上了 lock in share mode 共享锁,此时是当前读操作。当然,当我们 加排他锁的时候,也是当前读操作。
2). 快照读
简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据, 不加锁,是非阻塞读。
• Read Committed:每次select,都生成一个快照读。
• Repeatable Read:开启事务后第一个select语句才是快照读的地方。
• Serializable:快照读会退化为当前读。

在测试中,我们看到即使事务B提交了数据,事务A中也查询不到。 原因就是因为普通的select是快照 读,而在当前默认的RR隔离级别下,开启事务后第一个select语句才是快照读的地方,后面执行相同 的select语句都是从快照中获取数据,可能不是当前的最新数据,这样也就保证了可重复读。
3). MVCC
全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本, 使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需 要依赖于数据库记录中的三个隐式字段、undo log日志、readView。 接下来,我们再来介绍一下InnoDB引擎的表中涉及到的隐藏字段 、undolog 以及 readview,从 而来介绍一下MVCC的原理。
13.4.2 隐藏字段
14.4.2.1 介绍

当我们创建了上面的这张表,我们在查看表结构的时候,就可以显式的看到这三个字段。 实际上除了这三个字段以外,InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是:

13.4.3 undolog
13.4.3.1 介绍
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。 当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。 而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
13.4.3.2 版本链
有一张表原始数据为:

隐藏字段派上用场了
DB_TRX_ID : 代表最近修改事务ID,记录插入记录或最后一次修改该记录的事务ID,自增。
DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为null。

最终我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条 记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
13.4.4 readview
定义

版本链数据的访问规则

13.4.5 原理分析
13.4.5.1 RC隔离级别
RC隔离级别下,在事务中每一次执行快照读时生成ReadView。

13.4.5.2 RR隔离级别
RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的。

所以呢,MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog版本链、ReadView来实现的。 而MVCC + 锁,则实现了事务的隔离性。 而一致性则是由redolog 与 undolog保证。

14. MySQL管理
14.1 系统数据库
Mysql数据库安装完成后,自带了一下四个数据库,具体作用如下:

14.2 常用工具
14.2.1 mysql
该mysql不是指mysql服务,而是指mysql的客户端工具。
语法 : mysql [options] [database] 选项 : -u, --user=name #指定用户名 -p, --password[=name] #指定密码 -h, --host=name #指定服务器IP或域名 -P, --port=port #指定连接端口 -e, --execute=name #执行SQL语句并退出-e选项可以在Mysql客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本, 这种方式尤其方便。
mysql -uroot –p123456 db01 -e "select * from stu";
14.2.2 mysqladmin
mysqladmin 是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态、创建并删除数据库等。
语法: mysqladmin [options] command ... 选项: -u --user=name #指定用户名 -p --password[=name] #指定密码 -h --host=name #指定服务器IP或域名 -P --port=port #指定连接端口 示例
mysqladmin -uroot –p1234 drop 'test01'; mysqladmin -uroot –p1234 version;14.2.3 mysqlbinlog
由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使 用到mysqlbinlog 日志管理工具。

14.2.4 mysqlshow
mysqlshow 客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引。
语法 : mysqlshow [options] [db_name [table_name [col_name]]] 选项 : --count 显示数据库及表的统计信息(数据库,表 均可以不指定) -i 显示指定数据库或者指定表的状态信息 示例: #查询test库中每个表中的字段书,及行数 mysqlshow -uroot -p2143 test --count #查询test库中book表的详细情况 mysqlshow -uroot -p2143 test book --count14.2.5 mysqldump
mysqldump 客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及 插入表的SQL语句。
语法 : mysqldump [options] db_name [tables] mysqldump [options] --database/-B db1 [db2 db3...] mysqldump [options] --all-databases/-A 连接选项 : -u, --user=name 指定用户名 -p, --password[=name] 指定密码 -h, --host=name 指定服务器ip或域名 -P, --port=# 指定连接端口 输出选项: --add-drop-database 在每个数据库创建语句前加上 drop database 语句 --add-drop-table 在每个表创建语句前加上 drop table 语句 , 默认开启 ; 不 开启 (--skip-add-drop-table) -n, --no-create-db 不包含数据库的创建语句 -t, --no-create-info 不包含数据表的创建语句 -d --no-data 不包含数据 -T, --tab=name 自动生成两个文件:一个.sql文件,创建表结构的语句;一 个.txt文件,数据文件14.2.6 mysqlimport/source
1). mysqlimport
mysqlimport 是客户端数据导入工具,用来导入mysqldump 加 -T 参数后导出的文本文件。
语法 : mysqlimport [options] db_name textfile1 [textfile2...] 示例 : mysqlimport -uroot -p2143 test /tmp/city.txt2). source
如果需要导入sql文件,可以使用mysql中的source 指令 :
source /root/xxxxx.sql15. 日志
15.1 错误日志
错误日志是 MySQL 中最重要的日志之一,它记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。
该日志是默认开启的,默认存放目录 /var/log/,默认的日志文件名为 mysqld.log。查看日志位置:
show variables like '%log_error%';
15.2 二进制日志
15.2.1 介绍
二进制日志(BINLOG)记录了所有的 DDL语句和 DML语句,但不包括数据查询语句。
作用:
- 灾难时的数据恢复;
- MySQL 的主从复制。
在 MySQL8 版本中,默认二进制日志是开启着的,涉及到的参数如下:
show variables like '%log_bin%';
参数说明:
log_bin_basename:当前数据库服务器的 binlog 日志的基础名称(前缀),具体的 binlog 文件名需要在该 basename 的基础上加上编号(编号从 000001 开始)。log_bin_index:binlog 的索引文件,里面记录了当前服务器关联的 binlog 文件有哪些。
15.2.2 格式
MySQL 服务器中提供了多种格式来记录二进制日志,具体格式及特点如下:

查看二进制日志格式:
show variables like '%binlog_format%';如果需要配置二进制日志的格式,只需要在 /etc/my.cnf 中配置 binlog_format 参数即可。
15.2.3 查看
由于日志是以二进制方式存储的,不能直接读取,需要通过二进制日志查询工具 mysqlbinlog 来查看,具体语法:

mysqlbinlog binlog000000215.2.4 删除
对于比较繁忙的业务系统,每天生成的 binlog 数据巨大,如果长时间不清除,将会占用大量磁盘空间。可以通过以下几种方式清理日志:

查看二进制日志过期时间参数:
show variables like '%binlog_expire_logs_seconds%';15.3 查询日志
查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的 SQL 语句。默认情况下,查询日志是未开启的。
如果需要开启查询日志,可以修改 MySQL 的配置文件 /etc/my.cnf 文件,添加如下内容:
该选项用来开启查询日志,可选值:0 或者 1;0 代表关闭,1 代表开启 general_log=1 设置日志的文件名,如果没有指定,默认的文件名为 host_name.log general_log_file=mysql_query.log
开启了查询日志之后,在 MySQL 的数据存放目录,也就是 /var/lib/mysql/ 目录下就会出现 mysql_query.log 文件。之后所有的客户端的增删改查操作都会记录在该日志文件之中,长时间运行后,该日志文件将会非常大。
15.4 慢查询日志
慢查询日志记录了所有执行时间超过参数 long_query_time设置值并且扫描记录数不小于 min_examined_row_limit的所有的 SQL 语句的日志,默认未开启。long_query_time 默认为 10 秒,最小为 0,精度可以到微秒。
如果需要开启慢查询日志,需要在 MySQL 的配置文件 /etc/my.cnf 中配置如下参数:
#慢查询日志 slow_query_log=1 #执行时间参数 long_query_time=2 #记录执行较慢的管理语句 log_slow_admin_statements =1 #记录执行较慢的未使用索引的语句 log_queries_not_using_indexes = 1 上述所有的参数配置完成之后,都需要重新启动 MySQL 服务器才可以生效。

16. 主从复制
16.1 概述
主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。
MySQL 支持一台主库同时向多台从库进行复制,从库同时也可以作为其他从服务器的主库,实现链状复制。

MySQL 复制的优点主要包含以下三个方面:
- 主库出现问题,可以快速切换到从库提供服务。
- 实现读写分离,降低主库的访问压力。
- 可以在从库中执行备份,以避免备份期间影响主库服务。
16.2 原理
MySQL 主从复制的核心就是二进制日志,具体的过程如下:

- Master 主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。
- 从库读取主库的二进制日志文件 Binlog,写入到从库的中继日志 Relay Log。
- Slave 重做中继日志中的事件,将改变反映到它自己的数据。
16.3 搭建
16.3.1 准备

192.168.200.200(master)开放指定的 3306 端口号:
firewall-cmd --zone=public --add-port=3306/tcp --permanent firewall-cmd --reload关闭服务器的防火墙:
systemctl stop firewalld systemctl disable firewalld192.168.200.201(slave)同样执行上述关闭防火墙操作。
准备好两台服务器之后,在上述的两台服务器中分别安装好 MySQL,并完成基础的初始化准备(安装、密码配置等操作)工作。其中:
- 192.168.200.200 作为主服务器 master
- 192.168.200.201 作为从服务器 slave
16.3.2 主库配置
修改配置文件 /etc/my.cnf:
# mysql 服务 ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,默认为 1 server-id=1 # 是否只读,1 代表只读,0 代表读写 read-only=0 # 忽略的数据,指不需要同步的数据库 # binlog-ignore-db=mysql # 指定同步的数据库 # binlog-do-db=db01重启 MySQL 服务器:
systemctl restart mysqld登录 mysql,创建远程连接的账号,并授予主从复制权限:
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务 CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456'; #为 'itcast'@'%' 用户分配主从复制权限 GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%'; 通过指令,查看二进制日志坐标:
show master status;
字段含义说明:
file:从哪个日志文件开始推送日志文件position:从哪个位置开始推送日志binlog_ignore_db:指定不需要同步的数据库
16.3.3 从库配置
修改配置文件 /etc/my.cnf:
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,和主库不一样即可 server-id=2 #是否只读,1 代表只读, 0 代表读写 read-only=1 重新启动 MySQL 服务:
systemctl restart mysqld登录 mysql,设置主库配置:
若 mysql 是 8.0.23 及之后版本,执行:
CHANGE REPLICATION SOURCE TO SOURCE_HOST='192.168.200.200', SOURCE_USER='itcast', SOURCE_PASSWORD='Root@123456', SOURCE_LOG_FILE='binlog.000004', SOURCE_LOG_POS=663; 若 mysql 是 8.0.23 之前的版本,执行:
CHANGE MASTER TO MASTER_HOST='192.168.200.200', MASTER_USER='itcast', MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000004', MASTER_LOG_POS=663; 参数对应关系如下:
| 参数名 | 含义 | 8.0.23 之前 |
|---|---|---|
| SOURCE_HOST | 主库 IP 地址 | MASTER_HOST |
| SOURCE_USER | 连接主库的用户名 | MASTER_USER |
| SOURCE_PASSWORD | 连接主库的密码 | MASTER_PASSWORD |
| SOURCE_LOG_FILE | binlog 日志文件名 | MASTER_LOG_FILE |
| SOURCE_LOG_POS | binlog 日志文件位置 | MASTER_LOG_POS |
开启同步操作:
#8.0.22 之后 start replica; #8.0.22 之前 start slave ;查看主从同步状态:
# 8.0.22 之后 show replica status ; # 8.0.22 之前 show slave status ;如果出现以下界面,说明主从复制的状态正常

恭喜你学习完毕!✿