MySQL学习笔记

DDL

库操作

创建

创建数据库:
sql 复制代码
CREATE DATABASE 数据库名;

如果库不存在则创建:

sql 复制代码
CREATE DATABASE IF NOT EXISTS 数据库名;
创建数据库并设置字符集:
sql 复制代码
CREATE DATABASE 数据库名 CHARACTER SET 字符集;
创建数据库并指定排序方式:
sql 复制代码
CREATE DATABASE 数据库名 COLLATE 排序方式;

创建数据库并设置字符集指定排序方式:

sql 复制代码
CREATE DATABASE 数据库名 CHARACTER SET 字符集 COLLATE 排序方式;

常见字符集(Character Set):

  • utf8:(也叫 utf8mb3 )早期版本字符集,可能无法覆盖所有的汉字。
  • utf8mb4(8版本以上一般默认这个):覆盖字符更多,甚至包含表情符号。

常见排序规则(Collate):

  • utf8mb4_0900_ai_ci:不区分大小写的排序规则(8版本以上默认)。
  • utf8mb4_0900_as_cs:区分大小写。

查看

查看当前所有库:
sql 复制代码
SHOW DATABASES;
查看当前使用库:
sql 复制代码
SELECT DATABASE();
查看指定库下所有表:
sql 复制代码
SHOW TABLES FROM 数据库名;
查看创建库的信息:
sql 复制代码
SHOW CREATE DATABASE 数据库名;
切换库 / 选中库:
sql 复制代码
USE 数据库名;

修改

一般是不能直接改库的名字的。

修改库编码字符集:
sql 复制代码
ALTER DATABASE 数据库名 CHARACTER SET 字符集;
修改库排序方式:
sql 复制代码
ALTER DATABASE 数据库名 COLLATE 排序方式;

修改库编码字符集和排序方式:

sql 复制代码
ALTER DATABASE 数据库名 CHARACTER SET 字符集 COLLATE 排序方式;
删除库:
sql 复制代码
DROP DATABASE 数据库名;

如果库存在则删除:

sql 复制代码
DROP DATABASE IF EXISTS 数据库名;

表操作

创建:

sql 复制代码
CREATE TABLE 表名 (
    列名 类型 [列可选约束] [COMMENT '列可选注释'],
    列名 类型 [列可选约束] [COMMENT '列可选注释']
) [表可选约束] [COMMENT '表可选注释'];
sql 复制代码
CREATE TABLE IF NOT EXISTS 表名 (
    列名 类型 [列可选约束] [COMMENT '列可选注释'],
    列名 类型 [列可选约束] [COMMENT '列可选注释']
) [表可选约束] [COMMENT '表可选注释'];

最后一列不加逗号 ','

数据类型补充

整数类型:

|---------------|------|
| 整数类型 | 存储字节 |
| TINYINT | 1 |
| SMALLINT | 2 |
| MEDIUMINT | 3 |
| INTEGER (INT) | 4 |
| BIGINT | 8 |

除 SMALLINT 和 INTEGER (INT)其他都是MySQL的方言。

unsigned 修饰符(写在类型后面)可以设置为对应的无符号类型。

浮点数类型:

|-------------|------|--------|-------|
| 浮点数类型 | 存储字节 | M | D |
| FLOAT(M,D) | 4 | <= 24 | <=8 |
| DOUBLE(M,D) | 8 | <=53 | <=30 |

M 指小数+整数位数,D指小数位数。

同样可以在类型后使用 unsigned 修饰,但是并不会影响表示范围。

由于二进制表示小数会存在精度的丢失,在特定条件下需要使用定点数来表示小数。

定点数类型:

|--------------|------|-------|-------|
| 类型 | 存储字节 | M | D |
| DECIMAL(M,D) | 动态计算 | <=60 | <=30 |

如果D为0,就会省略小数点和小数位。

字符串类型:

|------------|------|-------------------|
| 类型 | 特点 | 长度范围 |
| CHAR(M) | 固定长度 | 0<=M<=255(字符) |
| VARCHAR(M) | 可变长度 | MySQL中一行最多65535字节 |

CHAR 会对不足M位的数据用空格补齐。

VARCHAR 会额外使用1字节来辨别是否为空。

MySQL一行最多65535字节并不是指VARCHAR最大程度,而是指一整行数据(包含其他列)最多这些字节。

在旧版本(5以下)中,M指字节,5以后在表示字符数。

65535的限制肯定不能满足所有需求,因此还有(大文本类型),他们不占用65535,有自己的字符限制。

|------------|------------|------------|
| 类型 | 最大范围(字节) | 存储空间 |
| TINYTEXT | 255 | 额外+2 |
| TEXT | 65535 | 额外+2 |
| MEDIUMTEXT | 16777215 | 额外+3 |
| LONGTEXT | 4294967295 | 额外+4(最大4G) |

这些类型都是可变长度类型。

但是如果文件很大的话,更推荐直接使用文本文件。

时间类型:

|-----------|---------------------|----|
| 类型 | 日期格式 | 字节 |
| YEAR | YYYY或YY | 1 |
| DATE | YYYY-MM-DD | 3 |
| TIME | HH:MM:SS | 3 |
| DATETIME | YYYY-MM-DD HH:MM:SS | 8 |
| TIMESTAMP | YYYY-MM-DD HH:MM:SS | 4 |

TIMESTAMP 和 DATETIME 的区别除了大小不同,时间范围也不同,TIMESTAMP的范围更小,而且受时区影响。

使用时一般设置自动初始化和更新。

sql 复制代码
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'

这里 CURRENT_TIMESTAMP 实际上是一个时间函数(还有NOW()函数),MySQL会自动转换为DATETIME或TIMESTAMP,DEFAULT后面也能直接写时间如 '2020-01-01 00:00:00'。

但是YEAR不能使用函数进行自动初始化和更新,需要手动。

DATE使用CURRENT_DATE,

TIME使用CURRENT_TIME,

注意并没有CURRENT_DATETIME

枚举类型:

举例

sql 复制代码
字段名 ENUM('值1','值2','值3',...);

只能从这些之中选一个。

排序时会按照定义的顺序。

集合类型:
sql 复制代码
字段名 SET('值1','值2','值3','值4',...);

只能从这些值中选,但可以多个。

插入数据时如果多选,在一个 ''中用,隔开就行。

重复值会自动去重。

其他类型:

二进制 / 大对象类型;

JSON 类型;

等。

修改:

修改表名:
sql 复制代码
ALTER TABLE 表名 RENAME [TO] 新表名;
添加字段:
sql 复制代码
ALTER TABLE 表名 ADD 字段名 字段类型 [FIRST|AFTER 字段名];
修改字段名和类型:
sql 复制代码
ALTER TABLE 原字段名 新字段名 字段类型 [FIRST|AFTER 字段名];
修改字段类型:
sql 复制代码
ALTER TABLE 表名 MODIFY 字段类型 [FIRST|AFTER 字段名];
删除字段:
sql 复制代码
ALTER TABLE 表名 DROP 字段名;
删除表:
sql 复制代码
DROP TABLE [IF EXISTS] 表名1 [,表名2];
清空表:
sql 复制代码
TRUNCATE TABLE 表名;

将会快速清空表,重置自增,无法回滚,不会出发任何触发器,表级锁。(不能同时清除多个)

DDL综合示例

建库

sql 复制代码
-- 1. 创建数据库(带字符集+排序)
CREATE DATABASE IF NOT EXISTS shop
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;

-- 2. 切换库
USE shop;

-- 3. 查看所有库
SHOW DATABASES;

-- 4. 查看当前库
SELECT DATABASE();

-- 5. 查看建库语句
SHOW CREATE DATABASE shop;

-- 6. 修改库字符集
ALTER DATABASE shop CHARACTER SET utf8mb4;

-- 7. 修改库排序规则
ALTER DATABASE shop COLLATE utf8mb4_0900_ai_ci;

建表

sql 复制代码
CREATE TABLE IF NOT EXISTS product (
    name VARCHAR(32) COMMENT '商品名称',
    price DECIMAL(10,2) COMMENT '价格(定点数,精确)',
    stock INT COMMENT '库存(整数)',
    weight DOUBLE COMMENT '重量(浮点数)',
    status TINYINT COMMENT '状态 0=下架 1=上架',
    intro TEXT COMMENT '商品简介(大文本)',
    create_year YEAR COMMENT '创建年份',
    create_time DATE DEFAULT CURRENT_DATE COMMENT '创建日期',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT '商品表';

修改表

sql 复制代码
-- 1. 修改表名
ALTER TABLE product RENAME product_new;

-- 改回去
ALTER TABLE product_new RENAME product;

-- 2. 添加字段
ALTER TABLE product ADD brand VARCHAR(20) COMMENT '品牌';

-- 3. 修改字段名 + 类型
ALTER TABLE product CHANGE brand goods_brand VARCHAR(30);

-- 4. 修改字段类型(不改名字)
ALTER TABLE product MODIFY goods_brand VARCHAR(50);

-- 5. 删除字段
ALTER TABLE product DROP goods_brand;

-- 6. 查看表结构(辅助查看)
DESC product;
sql 复制代码
-- 1. 清空表(删除所有数据,保留表结构)
TRUNCATE TABLE product;

-- 2. 删除表
DROP TABLE IF EXISTS product;

DML

行一般是操作的基本单位,插入、修改都是以行为基本单位。

数据插入

字符串类型需要用 ' ' 包住。

插入一整行数据:

sql 复制代码
INSERT INTO 表名 VALUES(value1,value2...);

这样写的话value1,value2必须和表的顺序一致,类型一致。

按指定字段插入一行数据:

sql 复制代码
INSERT INTO 表名(列名1,列名2...) VALUES(value1,value2...);

顺序和类型与指定的列名顺序和类型一致就行了。

插入多行:

sql 复制代码
INSERT INTO 表名 VALUES(value1,value2...),(value1,value2...)...;
sql 复制代码
INSERT INTO 表名(列名1,列名2...) VALUES(value1,value2...),(value1,value2...)...;

虽然VALUE也可以,但是标准SQL语句是VALUES。

数据修改

修改整张表的所有行:

sql 复制代码
UPDATE 表名 SET 列名1=值[,列名2=值...];

修改所有行中的某些字段值。

sql 复制代码
UPDATE 表名 SET 列名1=值[,列名2=值]... WHERE condition;

数据删除

删除表中所有行:

sql 复制代码
DELETE FROM 表名;

可以回滚,自增将保留,会触发DELETE触发器,行级锁。(不能同时对多个表操作)

sql 复制代码
DELETE FROM 表名 WHERE condition;

DML综合示例

sql 复制代码
-- DML 完整综合案例(全部写到一起)
CREATE DATABASE IF NOT EXISTS testdb CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE testdb;

CREATE TABLE IF NOT EXISTS user (
    name VARCHAR(20) COMMENT '姓名',
    age TINYINT COMMENT '年龄',
    score DECIMAL(5,2) COMMENT '分数',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) COMMENT '用户表';

-- 插入整行数据
INSERT INTO user VALUES('张三',20,90.5,NOW());
-- 指定字段插入
INSERT INTO user(name,age,score) VALUES('李四',21,85.0);
-- 插入多行数据
INSERT INTO user(name,age) VALUES('王五',19),('赵六',22),('孙七',18);

-- 条件修改
UPDATE user SET age=25, score=99.9 WHERE name='张三';
-- 全表修改
UPDATE user SET score=60.0;

-- 条件删除
DELETE FROM user WHERE name='李四';
-- 删除全表数据
DELETE FROM user;

-- 清空表
TRUNCATE TABLE user;
-- 删除表
DROP TABLE IF EXISTS user;

DQL

DQL操作时会基于原始数据查询出一个虚拟表。

DQL和DML都不会影响表结构,DQL又不会影响原始数据。

单表查询

基础查询语法

查询数据使用 SELECT。

非表查询:
sql 复制代码
SELECT 1;
SELECT 9/2;
SELECT VERSION();
SELECT NOW();

直接控制台输出1;4;MySQL版本。

查询指定表:
sql 复制代码
SELECT 列名1,列名2... FROM 表名;
SELECT * FROM 表名;

*表示全部列。

如果查询多表,一般使用 表名.列名 或 表名.* 来区分不同表的字段。

去重查询:
sql 复制代码
SELECT DISTINCT 列名1,列名2... FROM 表名;

多个字段去重时,只有当这些行的这些字段都想相同是才会认为是相同的。

起别名查询:
sql 复制代码
SELECT 列名1 AS 别名1,列名2 AS 别名2... FROM 表名;
SELECT 列名1 别名1,列名2 别名2... FROM 表名;

AS 是可以省略不写的。

可以简化列名或者方便映射Java中类的属性。

另外给表名添加 " " 表示区分大小写。

比如:

sql 复制代码
SELECT user_name AS "userName" FROM user;

MySQL中列名是下划线风格,Java中实体类是驼峰风格

查询时可以添加运算符:

sql 复制代码
SELECT salary * 12 '年薪' FROM employee;

不仅可以让列与常量运算,与其他列也可以,但是注意其他列是否为空(NULL),

NULL *运算(任何)= NULL。

如果不想出现这种情况,可以使用 IFNULL:

sql 复制代码
SELECT salary * IFNULL(month , 0) 收入 FROM employee;
填充列查询:

还可以给查询到的数据额外添加指定的一列。

sql 复制代码
SELECT 列名 列名1别名, "中国" AS nationality FROM student;

AS可以省略。

如果有用到 * ,*一般放到最前面。

显示表结构:

sql 复制代码
DESC 表名;
DESCRIBE 表名;

显示结果:

|-------|------|------|-----|---------|----------------|
| Field | Type | Null | Key | Default | Extra |
| 字段名称 | 字段类型 | 能否为空 | 键类型 | 默认值 | 额外描述,比如时间的自动更新 |

筛选查询:

使用where 如:

sql 复制代码
SELECT * FROM employee WHERE salary > 9000;
SELECT * FROM employee WHERE salary > 9000 AND gender = '女';

常见运算符:

算术运算符:

省略 + ,- ,* 。

|---------|------|
| % , MOD | 取模 |
| / | 浮点除法 |
| DIV | 整数除法 |

如果除以 0 会返回 NULL 。

可以在 SELECT 列位置或 WHERE 后面使用。

比较运算符:

省略 < , > , <= , >= , = , IS NULL , IS NOT NULL , 。

|------------------------|---------------|
| <> , != | 不相等, != 是不标准的 |
| <=> | NULL安全等于,不标准 |
| (NOT) BETWEEN...AND... | 值是否在(不在)某范围内 |
| (NOT) IN () | 值是否在(不在)某一组值内 |
| (NOT) LIKE () | 模式匹配(模糊等于) |

比较的结果有 1,0,null 这三种,0和 null为 false。

字符串按ASCI码比较。

NULL安全等于的意思是:

在SQL中任何值与NULL比较结果都是NULL,所以MySQl中通过 <=> 可以比较 NULL <=> NULL,结果为true。

MySQl有隐式转换 '1' = 1 为 true;

IN(1,5) 表示在1,5这一组数据中,1或者5;

BETWEEN 1 AND 5 表示 在1到5的区间。

简单匹配有 % , _ 这两个通配符。

%:匹配0个或多个字符;

_:匹配一个字符。

比如:

sql 复制代码
SELECT 'a_a' LIKE 'aba';

SELECT 'a%' LIKE 'aaa';

SELECT '%' LIKE 'abc';
逻辑运算符:

(a,b) = (x,y) 等同于 (a = x) AND (b = y)

(a,b) <> (x,y) 等同于 (a <> x) OR (b <> y)

(a,b) <=> (x,y) 等同于 (a <=> x) AND (b <=> y)

|------------|------|
| AND , && | 逻辑且 |
| OR , || | 逻辑或 |
| ! , NOT | 否 |
| XOR | 逻辑异或 |

分组查询:

sql 复制代码
SELECT 分组列,聚和函数
FROM table 
[WHERE condition]
[Group BY 分组列 HAVING 分组后条件]

示例:

按部门统计薪资高于3000的员工平均工资高于5000的部门、部门人数、平均工资

sql 复制代码
SELECT
    department_id,
    AVG(salary) AS 平均工资,
    COUNT(*) AS 员工人数
FROM employees
WHERE salary > 3000
GROUP BY department_id
HAVING AVG(salary) > 5000;

排序查询:

sql 复制代码
ORDER BY 排序列 ASC|DESC, 排序列 ASC|DESC

对多列进行排序时只有前一列相同才会对后一列进行排序

默认就是ASC(从小到大)

示例:

按年份统计订单年份、总销售额、最大订单并按照年份排序

sql 复制代码
SELECT
    YEAR(order_date) AS 订单年份,
    SUM(order_amount) AS 总销售额,
    MAX(order_amount) AS 最大订单
FROM orders
GROUP BY YEAR(order_date)
ORDER BY 订单年份;

分页查询(数据切割)

sql 复制代码
LIMIT 偏移量 行数

偏移量默认为0(就是从头开始查),可以将偏移量理解为从哪里开始查,

行数必写,就是查几行。

例如:

sql 复制代码
SELECT * FROM goods LIMIT 20,10;
SELECT * FROM goods LIMIT 10;

函数:

基础函数

很多函数跟其他编程语言的函数类似。

|------------------------|---------------------|
| ABS(x) | |
| SIGN(x) | 返回x的符号,正:1,负:-1,0:0 |
| PI() | |
| CEIL(x),CEILING(x) | |
| FLOOR(x) | |
| LEAST(e1,e2,e3,...) | |
| GREATEST(e1,e2,e3,...) | |
| MOD(x,y) | |
| RAND() | 0~1的随机值,可以设置种子 |
| ROUND(x) | 四舍五入,可以添加y设置小数位数 |
| TUENCATE(x,y) | 截断为y位小数 |
| SQRT(x) | |

|-------------------------------------|---------------------------------------|
| CHAR_LENGTH(s),CHARACTER_LENGTH(s) | 返回字符数 |
| LENGTH(s) | 返回字节数 |
| CONNECT(s1,s2,...) | |
| INSERT(str,idx,len,replacestr) | |
| REPLACE(s,a,b) | |
| UPPER(s),UCASE(s) | |
| LOWER(s),LCASE(s) | |
| LEFT(s,n) | 返回字符串左侧n个字符 |
| RIGHT(s,n) | |
| TRIM(s) | 去掉s开头和结尾的空格 |
| NULLIF(s1,s2) | 如果s1=s2,返回NULL,否则返回s1 |
| REVERSE(s) | |
| SUBSTR(s,idx,len),SUBSTRING(),MID() | 从s的idx位置取len个字符 |
| FIND_IN_SET(s1,s2) | s2是逗号分割的字符串列表,没找到返回0,找到返回位置,NULL则NULL |

当字段类型是集合时就可以使用,

sql 复制代码
FIND_IN_SET('值',列名) FROM 表名;

时间函数有很多,这不写了,只要知道一年中第几周,几天,以及周几,季度,月份等都是可以获取的(传data)。

流程控制函数
IF
sql 复制代码
IF(condition,true_value,false_value)

当condition条件成立为true时,返回true_value。

IFNULL
sql 复制代码
IFNULL(column,null_value)

当column列为空时,null_value值为结果

CASE
sql 复制代码
CASE
  WHERE condition1 THEN result1
  WhERE condition2 THEN result2
  ELSE default result
END[AS new_name]
sql 复制代码
CASE expr
  WHERE value1 THEN result1
  WHERE value2 THEN result2
  ELSE default result
END [AS new_name]
多行函数(聚和)

|---------|----------------|
| avg | 求列数据平均值 |
| sum | 求列的数据和 |
| min/max | 求列中的最大最小值 |
| count | 计算满足某个条件的列有多少行 |

sql 复制代码
SELECT COUNT(*),COUNT(1),COUNT(havejob) FROM `people`;

统计总人数,总人数,有工作的人的人数

单表查询综合练习

1.创建数据库test04_lib

sql 复制代码
CREATE DATABASE IF NOT EXISTS test04_lib;
USE test04_lib;

2.创建books表

sql 复制代码
CREATE TABLE books (
    id INT,
    name VARCHAR(50),
    authors VARCHAR(100),
    price FLOAT,
    pubdate YEAR,
    note VARCHAR(100),
    num INT
);

3.向 books 表插入记录

sql 复制代码
-- 1) 不指定字段插入第一条记录
INSERT INTO books VALUES (1, 'Tal of AAA', 'Dickes', 23, 1995, 'novel', 11);

-- 2) 指定字段插入第二条记录
INSERT INTO books (id, name, authors, price, pubdate, note, num) 
VALUES (2, 'EmmaT', 'Jane lura', 35, 1993, 'joke', 22);

-- 3) 批量插入剩余记录
INSERT INTO books VALUES 
(3, 'Story of Jane', 'Jane Tim', 40, 2001, 'novel', 0),
(4, 'Lovey Day', 'George Byron', 20, 2005, 'novel', 30),
(5, 'Old land', 'Honore Blade', 30, 2010, 'law', 0),
(6, 'The Battle', 'Upton Sara', 30, 1999, 'medicine', 40),
(7, 'Rose Hood', 'Richard haggard', 28, 2008, 'cartoon', 28);

4.将小说类型 (novel) 的书的价格都增加 5

sql 复制代码
UPDATE books SET price = price + 5 WHERE note = 'novel';

5.将名称为 EmmaT 的书的价格改为 40,并将说明改为 drama

sql 复制代码
UPDATE books SET price = 40, note = 'drama' WHERE name = 'EmmaT';

6.删除库存为 0 的记录

sql 复制代码
DELETE FROM books WHERE num = 0;

7.统计书名中包含 a 字母的书

sql 复制代码
SELECT * FROM books WHERE name LIKE '%a%';

8.统计书名中包含 a 字母的书的数量和库存总量

sql 复制代码
SELECT COUNT(*) AS 数量, SUM(num) AS 库存总量 
FROM books WHERE name LIKE '%a%';

9.找出 "novel" 类型的书,按照价格降序排列

sql 复制代码
SELECT * FROM books WHERE note = 'novel' ORDER BY price DESC;

10.查询图书信息,按库存量降序排列,库存相同按 note 升序排列

sql 复制代码
SELECT * FROM books ORDER BY num DESC, note ASC;

11.按照 note 分类统计书的数量

sql 复制代码
SELECT note, COUNT(*) AS 数量 FROM books GROUP BY note;

12.按照 note 分类统计书的库存量,显示库存量超过 30 本的

sql 复制代码
SELECT note, SUM(num) AS 库存总量 
FROM books GROUP BY note HAVING SUM(num) > 30;

13.查询所有图书,每页显示 5 本,显示第二页

sql 复制代码
SELECT * FROM books LIMIT 5, 5;

14.按照 note 分类统计书的库存量,显示库存量最多的

sql 复制代码
SELECT note, SUM(num) AS 库存总量 
FROM books GROUP BY note ORDER BY 库存总量 DESC LIMIT 1;

15.查询书名达到 10 个字符的书(不包括空格)

sql 复制代码
SELECT * FROM books WHERE LENGTH(REPLACE(name, ' ', '')) >= 10;

16.查询书名和类型,note 值转中文显示

sql 复制代码
SELECT 
    name,
    CASE note
        WHEN 'novel' THEN '小说'
        WHEN 'law' THEN '法律'
        WHEN 'medicine' THEN '医药'
        WHEN 'cartoon' THEN '卡通'
        WHEN 'joke' THEN '笑话'
        WHEN 'drama' THEN '戏剧'
        ELSE note
    END AS 类型
FROM books;

17.查询书名、库存,根据 num 值显示不同状态

sql 复制代码
SELECT 
    name,
    num,
    CASE
        WHEN num > 30 THEN '滞销'
        WHEN num > 0 AND num < 10 THEN '畅销'
        WHEN num = 0 THEN '需要无货'
        ELSE '正常'
    END AS 状态
FROM books;

18.统计每一种 note 的库存量,并合计总量

sql 复制代码
-- 分组统计 + 合计总量(UNION实现)
SELECT note, SUM(num) AS 库存 FROM books GROUP BY note
UNION ALL
SELECT '合计' AS note, SUM(num) AS 库存 FROM books;

UNION 规则

  1. 两个查询的列数必须一样

  2. 列的类型要兼容

  3. UNION ALL = 直接拼接

  4. UNION = 拼接 + 去重

他的作用就是将两张表拼接在一起。

也可以使用MySQL的 WITH ROLLUP。

sql 复制代码
SELECT note, SUM(num) AS 库存
FROM books
GROUP BY note WITH ROLLUP;

、19.统计每一种 note 的数量,并合计总量

sql 复制代码
SELECT note, COUNT(*) AS 数量 FROM books GROUP BY note
UNION ALL
SELECT '合计' AS note, COUNT(*) AS 数量 FROM books;
sql 复制代码
SELECT note, COUNT(*) AS 数量 
FROM books 
GROUP BY note WITH ROLLUP;

20.统计库存量前三名的图书

sql 复制代码
SELECT * FROM books ORDER BY num DESC LIMIT 3;

21.找出最早出版的一本书

sql 复制代码
SELECT * FROM books ORDER BY pubdate ASC LIMIT 1;

22.找出 novel 中价格最高的一本书

sql 复制代码
SELECT * FROM books WHERE note = 'novel' ORDER BY price DESC LIMIT 1;

23.找出书名中字数最多的一本书(不含空格)

sql 复制代码
SELECT * FROM books ORDER BY LENGTH(REPLACE(name, ' ', '')) DESC LIMIT 1;

表的约束

域级约束

|-------|----------|-----|
| 非空约束 | NOT NULL | |
| 默认值约束 | DEFAULT | |
| 检查约束 | CHECK | 表级别 |

主键和唯一约束的列是不能用DEFAULT的

CHECK

check是表级别的,可以写在列后面,也可以列后单独写。

建表时添加 CHECK 约束

单字段约束

sql 复制代码
CREATE TABLE students (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    age INT CHECK (age >= 0 AND age <= 120), -- 年龄必须0-120之间
    score FLOAT CHECK (score >= 0 AND score <= 100) -- 分数必须0-100之间
);

多字段联合约束

sql 复制代码
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    total_price FLOAT,
    paid_price FLOAT,
    -- 实付金额不能超过总价,且不能为负数
    CONSTRAINT check_paid CHECK (paid_price >= 0 AND paid_price <= total_price)
);

可以给约束起名字,方便后续修改 / 删除

建表后添加 CHECK 约束
sql 复制代码
-- 语法:ALTER TABLE 表名 ADD CONSTRAINT 约束名 CHECK(条件);
ALTER TABLE books 
ADD CONSTRAINT check_price CHECK (price > 0); -- 价格必须大于0
删除 CHECK 约束
sql 复制代码
-- 语法:ALTER TABLE 表名 DROP CHECK 约束名;
ALTER TABLE books DROP CHECK check_price;
查看CHECK约束
sql 复制代码
DESC books;

DESC 只能查看数据类型、是否NOT NULL、默认值、主键(PRT)、唯一键(UNI)。

sql 复制代码
SHOW CREATE TABLE books;

可以通过查看建表语句查看所有约束。

也有专门查看所有约束的sql语句

sql 复制代码
SELECT *
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = '库名'
AND TABLE_NAME = '表名'
[AND constraint_type = ''](如果是索引可以再加上索引名)
;

实体(行)约束

|------|----------------|----------|
| 主键约束 | PRIMARY KEY | 主键唯一切不为空 |
| 唯一约束 | UNIQUE | 唯一约束 |
| 自增约束 | AUTO_INCREMENT | 自增长 |

UNIQUE

创建时如果没有自定义约束名,默认就是字段名

MySQL 中 NULL != NULL,所以不视为重复

建表时加 UNIQUE

单列唯一

sql 复制代码
CREATE TABLE product (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(32) UNIQUE COMMENT '商品名称,不允许重复',
    price DECIMAL(10,2)
);
sql 复制代码
CREATE TABLE product (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(32) UNIQUE KEY COMMENT '商品名称,不允许重复',
    price DECIMAL(10,2)
);

多列唯一

sql 复制代码
CREATE TABLE product (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(32),
    create_year YEAR,
    -- 单列唯一,自定义约束名
    CONSTRAINT uk_product_name UNIQUE KEY (name),
    -- 多列联合唯一(name + create_year 组合不重复)
    CONSTRAINT uk_product_name_year UNIQUE KEY (name, create_year)
);
建表后添加 UNIQUE
sql 复制代码
ALTER TABLE product
ADD CONSTRAINT uk_product_name UNIQUE (name);
sql 复制代码
ALTER TABLE product
ADD CONSTRAINT uk_product_name_year UNIQUE (name, create_year);
删除 UNIQUE 约束
sql 复制代码
ALTER TABLE emp7 
DROP CONSTRAINT emp7_name;

因为 UNIQUE 本质上也是一种索引,所以删除时可以用 DROP INDEX(MySQL中)

sql 复制代码
-- 用约束名删除
ALTER TABLE product
DROP INDEX uk_product_name;
PRIMARY KEY

主键每张表只有一个,但是可以设置复合主键,多个列组成一个主键。

主键查询效率一般比较高。

建表时添加主键
sql 复制代码
CREATE TABLE emp (
    id INT PRIMARY KEY COMMENT '员工编号,主键',
    name VARCHAR(20)
);
sql 复制代码
CREATE TABLE emp (
    id INT,
    dept_id INT,
    name VARCHAR(20),
    -- 联合主键:id + dept_id 组合唯一且非空
    PRIMARY KEY (id, dept_id)
);
建表后添加主键
sql 复制代码
-- 单字段主键
ALTER TABLE emp ADD PRIMARY KEY(id);

-- 多字段联合主键
ALTER TABLE emp ADD PRIMARY KEY(id, dept_id);
删除主键约束
sql 复制代码
ALTER TABLE emp DROP PRIMARY KEY;

如果主键字段同时是 AUTO_INCREMENT,需要先去掉自增属性,再删除主键

删除主键后,NOT NULL约束还是存在的。

sql 复制代码
-- 先去掉自增
ALTER TABLE emp MODIFY id INT;
-- 再删除主键
ALTER TABLE emp DROP PRIMARY KEY;
AUTO_INCREMENT

一张表中只能有一个自增约束,只能加在主键或唯一的列上(类型为整数类型)

删除数据后自增不会回退

  • 初始值:建表时可以用 AUTO_INCREMENT=100 指定初始值
  • 步长:系统变量 auto_increment_increment 控制,默认是 1
建表时添加自增
sql 复制代码
CREATE TABLE emp (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20)
);
sql 复制代码
CREATE TABLE emp (
    id INT UNIQUE KEY AUTO_INCREMENT,
    name VARCHAR(20)
);
sql 复制代码
CREATE TABLE emp (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20)
) AUTO_INCREMENT=100; -- 自增从100开始
建表后添加 / 删除自增
sql 复制代码
ALTER TABLE emp MODIFY id INT AUTO_INCREMENT;
sql 复制代码
ALTER TABLE emp MODIFY id INT; -- 去掉自增属性

引用(多表)约束

|----------|-------------|----------------------------------|
| 外键(参照)约束 | FOREIGN KEY | 限定表中某一列的值必须参照其他表(也可以是自己)的某一列(主键) |

FOREIGN KEY

被引用表为主表,引用表为从表

关系型数据库的关系就是指主外键关系

外键类型必须与引用的主键类型一致,一般命名相同。

删除主表数据前子表的引用数据需要先删除。或者。。。

设置外键关系会降低效率。

建表时添加外键
sql 复制代码
CREATE TABLE dept (
    dept_id INT PRIMARY KEY,
    dept_name VARCHAR(20)
);
CREATE TABLE emp (
    emp_id INT PRIMARY KEY,
    emp_name VARCHAR(20),
    dept_id INT,
    -- 给外键起个名字,方便后续删除
    CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) 
    REFERENCES dept(dept_id)
    ON UPDATE CASCADE  -- 可选:主表主键更新时,子表同步更新
    ON DELETE SET NULL -- 可选:主表记录删除时,子表外键设为NULL
);
建表后添加外键
sql 复制代码
ALTER TABLE emp 
ADD CONSTRAINT fk_emp_dept 
FOREIGN KEY (dept_id) 
REFERENCES dept(dept_id);
删除外键

一张表中可以有多个外键,所以删除时需要指定约束名。

查询外键约束名

sql 复制代码
SELECT constraint_name 
FROM information_schema.table_constraints 
WHERE table_name = 'emp' AND constraint_type = 'FOREIGN KEY';

删除外键约束

sql 复制代码
ALTER TABLE emp DROP FOREIGN KEY fk_emp_dept;

删除对应的索引

MySQL 会为外键自动创建索引,如果不需要了可以手动删除

sql 复制代码
-- 先查索引名
SHOW INDEX FROM emp;

-- 再删除索引
ALTER TABLE emp DROP INDEX fk_emp_dept;
外键约束级联设计
规则 核心作用 实战场景
CASCADE 主表更新 / 删除,子表同步更新 / 删除 主表部门 ID 修改时,员工表的部门 ID 自动跟着改;删除部门时,员工也跟着删(慎用)
SET NULL 主表更新 / 删除,子表外键设为 NULL 删除部门后,员工的部门字段变成空,方便后续处理(注意:子表外键不能是 NOT NULL
NO ACTION 子表有数据时,禁止主表更新 / 删除 主表操作会被立刻拒绝,必须先处理子表数据
RESTRICT(默认) NO ACTION 完全一样,立即检查约束 MySQL 默认行为,子表有数据时,主表删不掉、改不了
SET DEFAULT 主表变更时,子表外键设为默认值 MySQL 的 InnoDB 引擎不支持,不用考虑
sql 复制代码
CREATE TABLE emp (
    emp_id INT PRIMARY KEY,
    dept_id INT,
    CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) 
    REFERENCES dept(dept_id)
    ON UPDATE CASCADE   -- 部门ID修改,员工的部门ID自动更新
    ON DELETE RESTRICT  -- 部门有员工时,不允许删除部门
);

多表查询

多表合并

垂直合并

查询的列数和数据类型相同时就可以垂直合并

只有当所有列的数据都相同时才会认为是重复

sql 复制代码
-- 表1:一班学生成绩
CREATE TABLE class1 (
    name VARCHAR(20),
    score INT
);
INSERT INTO class1 VALUES 
('张三', 90),
('李四', 85),
('王五', 95);

-- 表2:二班学生成绩
CREATE TABLE class2 (
    name VARCHAR(20),
    score INT
);
INSERT INTO class2 VALUES 
('李四', 85),  -- 和class1里的李四完全一样,是重复行
('赵六', 88),
('孙七', 92);
UNION

自动去重

sql 复制代码
SELECT name, score FROM class1
UNION
SELECT name, score FROM class2;
UNION ALL

不去重

sql 复制代码
SELECT name, score FROM class1
UNION ALL
SELECT name, score FROM class2;
水平合并(连接)
sql 复制代码
-- 用户表(主表)
CREATE TABLE users (
    user_id INT PRIMARY KEY,
    name VARCHAR(20)
);
INSERT INTO users VALUES (1, '张三'), (2, '李四'), (3, '王五');

-- 订单表(外键表)
CREATE TABLE order_info (
    order_id INT PRIMARY KEY,
    user_id INT,
    amount INT
);
INSERT INTO order_info VALUES (101, 1, 100), (102, 1, 200), (103, 2, 150), (104, 4, 50);

连接时因为多个表中的列可能存在相同的名称,所以一般 表名.列名 来区别。

内连接(INNER JOIN)

只保留两张表中匹配条件成立的行,不匹配的会被过滤掉

类型 语法示例 特点
标准写法 select * from 表1 [inner] join 表2 on 表1.主键 = 表2.外键 可读性好、通用性强,支持复杂条件,是 SQL 标准写法
非标准写法 select * from 表1,表2 where 表1.主键 = 表2.外键 语法简单,但不直观,复杂多表查询容易出错,不推荐

inner关键字可以省略

sql 复制代码
SELECT u.name, o.amount
FROM users u
INNER JOIN order_info o 
ON u.user_id = o.user_id;

如果合并多张表只需要末尾再添加JOIN语句就行。

ON就是表示条件可以使用 where 添加更多条件。

sql 复制代码
SELECT u.name, o.amount
FROM users u, order_info o 
WHERE u.user_id = o.user_id;

可以AND等添加额外筛选条件

外连接

外连接分左右外连接

  • 左外连接(LEFT JOIN) :保留左表(FROM 后面的表)的所有行,右表匹配不到的字段会显示 NULL(可以通过 IFNULL 设置替代值)
  • 右外连接(RIGHT JOIN) :保留右表(JOIN 后面的表)的所有行,左表匹配不到的字段会显示 NULL
sql 复制代码
SELECT u.name, o.amount
FROM users u
LEFT JOIN order_info o 
ON u.user_id = o.user_id;
sql 复制代码
SELECT u.name, o.amount
FROM users u
RIGHT JOIN order_info o 
ON u.user_id = o.user_id;
自然连接

自动根据两张表中列名和类型都相同的列 进行匹配,不用写 ON 条件

自然连接也分有自然内和自然左右连接

sql 复制代码
-- 自然内连接
SELECT * FROM emp NATURAL JOIN dept;

-- 自然左外连接
SELECT * FROM emp NATURAL LEFT JOIN dept;

-- 自然右外连接
SELECT * FROM emp NATURAL RIGHT JOIN dept;

因为自动匹配有时候并不是我们想要的

可以用 USING子句设置

sql 复制代码
-- 只按 department_id 这一个同名字段连接
SELECT * FROM employees e JOIN departments d USING (department_id);

子查询

标量子查询(单行单列)

查询分数高于全班平均分的学生

sql 复制代码
-- 子查询先算出平均分(标量),外层再用它做比较
SELECT student_name, score
FROM t_score
WHERE score > (
    SELECT AVG(score) 
    FROM t_score
);
行子查询(单行多列)

找到和学生 id=3 同名、同分数的所有学生

sql 复制代码
-- 子查询返回一行(name, score),外层 WHERE 整体匹配这两个字段
SELECT *
FROM t_score
WHERE (student_name, score) = (
    SELECT student_name, score 
    FROM t_score 
    WHERE student_id = 3
);
列子查询(一列多行)

查询所有和「分数大于 90 分的学生」分数相同的学生

sql 复制代码
SELECT *
FROM t_score
WHERE score IN (
    SELECT score 
    FROM t_score 
    WHERE score > 90
);

IN 会匹配所有等于这些值的记录。

查询分数大于所有不及格学生分数的学生(也就是分数比最低及格分还高)

sql 复制代码
SELECT *
FROM t_score
WHERE score > ALL (
    SELECT score 
    FROM t_score 
    WHERE score < 60
);
  • > ALL 表示 "比子查询返回的所有值都大",等价于 score > MAX(不及格分数)
  • > ANY 表示 "比子查询返回的任意一个值大",等价于 score > MIN(不及格分数)
表子查询(多行多列)

先按班级算出每个班的平均分,再查询出「班级平均分高于年级平均分」的班级和分数

sql 复制代码
-- 第一步:子查询先按班级分组,算出每个班的平均分(多行多列的结果集)
-- 第二步:外层把它当成临时表,再和年级平均分做比较
SELECT class_id, class_avg
FROM (
    SELECT class_id, AVG(score) AS class_avg
    FROM t_score
    GROUP BY class_id
) AS class_avg_table -- 表子查询必须加别名!
WHERE class_avg > (SELECT AVG(score) FROM t_score);

这里加别名是因为子查询也是针对的同一个表,不能同时对一个表进行不同的操作,所以取别名避免。

多表查询综合练习

1.查询每位学生的姓名、年龄、所选课程的名称以及对应的成绩

sql 复制代码
SELECT s.name AS 姓名, s.age AS 年龄, c.course_name AS 课程名称, sc.score AS 成绩
FROM students s
LEFT JOIN scores sc ON s.student_id = sc.student_id
LEFT JOIN courses c ON sc.course_id = c.course_id;

2.查询没有选择任何课程的学生姓名

sql 复制代码
SELECT name
FROM students
WHERE student_id NOT IN (SELECT DISTINCT student_id FROM scores);

DISTINCT 是为了避免重复的 student_id,提升查询效率,不写也能运行,但会多做一些无意义的匹配。

sql 复制代码
SELECT s.name
FROM students s
LEFT JOIN scores sc ON s.student_id = sc.student_id
WHERE sc.student_id IS NULL;

3.查询每门课程的平均成绩,并列出平均成绩高于所有学生平均成绩的课程

sql 复制代码
SELECT course_id, AVG(score) AS 平均成绩
FROM scores
GROUP BY course_id
HAVING AVG(score) > (SELECT AVG(score) FROM scores);

4.查询每位学生所选课程的平均成绩,并按照平均成绩降序排列

sql 复制代码
SELECT s.name AS 姓名, AVG(sc.score) AS 平均成绩
FROM students s
LEFT JOIN scores sc ON s.student_id = sc.student_id
GROUP BY s.name
ORDER BY 平均成绩 DESC;

数据库事务

自动提交事务

MySQL 默认是 自动提交事务(autocommit):

  • 每一条 SQL 语句都是一个独立的事务,执行成功就自动提交,修改永久生效;执行失败就自动回滚,数据不会变化。
  • 但如果我们要执行多条 SQL,一般需要要么全部成功,要么全部失败,就需要把这些 SQL 放到同一个事务里,这就是我们要手动控制事务的原因。
操作 SQL 语句 作用
关闭自动提交 SET autocommit = false;SET autocommit = 0; 之后的所有 SQL 都不会自动提交,需要手动执行COMMIT才会生效
开启自动提交 SET autocommit = true;SET autocommit = 1; 恢复默认模式,SQL 执行成功就自动提交
查看当前状态 SHOW VARIABLES LIKE 'autocommit'; 检查当前是否开启了自动提交
sql 复制代码
-- 1. 关闭自动提交,进入手动事务模式
SET autocommit = false;

-- 2. 执行你的业务SQL(多条操作)
UPDATE t_employee SET salary = 15000 WHERE ename = '孙红梅';
-- 可以继续执行其他SQL,比如:
UPDATE t_employee SET salary = 10000 WHERE ename = '张三';

-- 3. 手动提交:所有修改永久生效
COMMIT;

-- 或 3. 手动回滚:所有修改全部撤销,数据恢复原样
ROLLBACK;
  • SET autocommit = false会话级的设置,只对当前数据库连接生效,关闭连接后会恢复默认值。
  • 如果忘记执行 COMMIT,事务会一直处于 "未提交" 状态,其他客户端看不到修改后的数据,直到提交或回滚,或者连接断开自动回滚。

MySQL 还支持用 BEGIN / START TRANSACTION 显式开启事务,这种方式更清晰,不会影响全局的 autocommit 设置:

sql 复制代码
-- 1. 显式开启一个事务
BEGIN; -- 或 START TRANSACTION;

-- 2. 执行业务SQL
UPDATE account SET balance = balance - 100 WHERE id = 1; -- 扣钱
UPDATE account SET balance = balance + 100 WHERE id = 2; -- 加钱

-- 3. 提交事务,修改永久生效
COMMIT;

-- 如果中间出错,回滚事务
ROLLBACK;

注意事务只能对数据操作起效,DDL(对表库的修改和创建删除)是无效的

事务隔离

隔离级别 核心特点 脏读 不可重复读 幻读 典型数据库
read-uncommitted(读未提交) 直接读其他事务的未提交数据 极少使用
read-committed(读已提交) 只能读其他事务已提交的数据 ✅ (Oracle) Oracle、SQL Server 默认
repeatable-read(可重复读) 事务内多次读取同一行数据结果一致 ⚠️ (MySQL) MySQL 默认
serializable(串行化) 事务串行执行,完全隔离 并发性能最差
问题 通俗解释 举个例子
脏读 一个事务读到了另一个事务未提交的数据 事务 A 修改了数据但没提交,事务 B 读到了这个修改后的值,结果 A 回滚了,B 读到的就是 "脏数据"
不可重复读 同一个事务内,两次读取同一行数据,结果不一样 事务 A 两次查询同一行数据,中间事务 B 修改并提交了这行数据,导致 A 两次结果不一致
幻读 同一个事务内,两次查询的结果集数量不一样,像出现了 "幻觉" 事务 A 查询符合条件的记录,中间事务 B 新增 / 删除了符合条件的记录并提交,导致 A 两次查询的行数不同

权限控制

MySQL 通过权限来限制用户对数据库和表的访问,权限可以授予到 3 个层级

  1. 数据库级别:控制用户对整个数据库的访问
  2. 表级别:控制用户对单张表的访问
  3. 特定操作级别:控制用户对某张表的某类操作(比如仅允许查询,不允许修改)
权限项 权限介绍 风险等级
SELECT 允许用户查询表中的数据
INSERT 允许用户向表中插入新数据
UPDATE 允许用户更新表中的现有数据
DELETE 允许用户从表中删除数据
CREATE 允许用户创建新的数据库或表
DROP 允许用户删除数据库或表 极高
GRANT OPTION 允许用户将自己拥有的权限授予其他用户 极高
ALL PRIVILEGES 允许用户执行所有操作 极高

1. 创建用户

sql 复制代码
-- 创建用户,指定主机(localhost表示仅本地访问)
CREATE USER 'test_user'@'localhost' IDENTIFIED BY '123456';

'%'替换'localhost'可以表示任意地址访问

2. 授予权限(按层级)

sql 复制代码
-- ① 数据库级别:授予用户对test_db数据库的所有权限
GRANT ALL PRIVILEGES ON test_db.* TO 'test_user'@'localhost';

-- ② 表级别:授予用户对test_db.student表的查询、插入、更新权限
GRANT SELECT, INSERT, UPDATE ON test_db.student TO 'test_user'@'localhost';

-- ③ 特定操作:授予用户对test_db.student表的查询权限(仅允许查询)
GRANT SELECT ON test_db.student TO 'test_user'@'localhost';

-- 授予用户给其他用户授权的权限(GRANT OPTION)
GRANT SELECT ON test_db.* TO 'test_user'@'localhost' WITH GRANT OPTION;

这里 test_db.* 表示「指定数据库下的所有表」,如果是 *.* 就是所有数据库所有表。

3. 查看用户权限

sql 复制代码
-- 查看当前用户权限
SHOW GRANTS;

-- 查看指定用户权限
SHOW GRANTS FOR 'test_user'@'localhost';

查看所有用户列表

sql 复制代码
SELECT User, Host FROM mysql.user;

4. 撤销权限

sql 复制代码
-- 撤销用户对test_db.student表的DELETE权限
REVOKE DELETE ON test_db.student FROM 'test_user'@'localhost';

-- 撤销用户的所有权限
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'test_user'@'localhost';

5. 删除用户

sql 复制代码
DROP USER 'test_user'@'localhost';

数据备份和还原

全量备份

sql文件备份(mysqldump

它是 MySQL 自带的备份工具,能把数据和表结构导出成 .sql 文件,必须在 CMD/Terminal 中执行(不能登录 MySQL 后执行)

备份场景 命令示例
备份单库单表 mysqldump -u 用户名 -p 数据库名 表名 > 备份文件.sql
备份单库多表 mysqldump -u 用户名 -p 数据库名 表1 表2 > 备份文件.sql
备份单库所有表 mysqldump -u 用户名 -p 数据库名 > 备份文件.sql

可以用 --all-databases 参数一次性备份所有库

sql 复制代码
mysqldump -uroot -p --all-databases > all_backup.sql
全量恢复(mysql 命令)
sql 复制代码
mysql -u 用户名 -p 目标数据库名 < 备份文件.sql
  • **目标数据库必须提前存在,**恢复前要先登录 MySQL,手动创建好数据库
  • MySQL 版本要兼容

binlog日志

biolog配置文件设置

Binlog 是 MySQL 的二进制运行日志,核心特点是:

  • 会记录数据库中所有写操作(增 / 删 / 改,不包括查询),同时记录了操作的执行时间、执行内容。
  • 数据以二进制格式存储,本身不能直接查看,需要用工具解析。
  • 它的三大核心用途:
    1. 误删数据恢复:通过日志回放,把误删除 / 修改的数据恢复到之前的状态。
    2. 增量备份:结合全量备份,实现 "全量备份 + 增量日志" 的完整备份方案。
    3. 主从同步:主库把 Binlog 同步给从库,从库回放日志,实现数据复制。

MySQL 8.0 版本中 Binlog 默认是开启的,我们可以通过配置文件确认 / 修改设置:

配置文件位置(Windows 示例)

默认路径:C:\ProgramData\MySQL\MySQL Server 8.x\my.ini

注:ProgramData 是隐藏文件夹,需要先开启 "显示隐藏文件" 才能看到。

[mysqld] 模块下添加 / 确认以下配置:

XML 复制代码
[mysqld]
# 开启二进制日志,指定日志文件前缀为 mysql-bin
log-bin=mysql-bin
# 指定数据目录,日志文件会存在这里
datadir=C:/ProgramData/MySQL/MySQL Server 8.0/Data
# 可选:设置日志过期时间(单位:秒,如 7天=604800)
expire_logs_days=7
Binlog 清单文件查看

找到对应的 Binlog 文件

先登录 MySQL然后

sql 复制代码
# 查看当前正在使用的 Binlog 文件:
SHOW MASTER STATUS;
# 查看所有的 Binlog 文件:
SHOW BINARY LOGS;
# 查看 my_logbin.000002 中的内容(如果不指定文件,默认就是 000001)
SHOW BINLOG EVENTS IN 'my_logbin.000002';

EVENTS 显示结果如下:

除了指定文件,还可以进一步指定 pos

sql 复制代码
SHOW BINLOG EVENTS IN 'my_logbin.000002' FROM 391 LIMIT 1, 2;

LIMIT 1,2 表示从pos位置开始的第2到第3个操作:

Binlog 清单文件导出sql脚本文件

mysqlbinlog需要在cmd中使用,不能在数据库连接中使用。

导出整个 Binlog 为 SQL

sql 复制代码
mysqlbinlog my_logbin.000001 > d:/bin001.sql

导出【删除之前】的所有正确操作

sql 复制代码
mysqlbinlog --stop-position=391 my_logbin.000002 > d:\bin002.sql

导出【删除之后】的所有正确操作

sql 复制代码
mysqlbinlog --start-position=441 my_logbin.000002 > d:\bin003.sql

把三段 SQL 恢复到数据库

sql 复制代码
mysql -uroot -p test08_binlog < d:\bin001.sql
mysql -uroot -p test08_binlog < d:\bin002.sql
mysql -uroot -p test08_binlog < d:\bin003.sql
参数 作用
--start-position=数字 从这个位置开始导出
--stop-position=数字 到这个位置停止导出
> 文件名.sql 导出保存为 SQL 文件
mysql 库名 < 文件.sql 把 SQL 恢复进数据库

窗口函数

窗口函数是 MySQL 中一种不改变查询结果行数的高级分析功能,核心特点:

  • 它不像 GROUP BY 那样把多行合并成一行,而是保留每一行的原始数据
  • 同时能在 "窗口"(分组)内,执行排序、聚合、前后行取值等分析操作
  • 必须搭配 OVER() 子句使用,用来定义窗口的分组和排序规则

语法 1:直接定义窗口(最常用)

sql 复制代码
多行函数 OVER ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
  • OVER():窗口函数的标志,括号里用来定义窗口规则
  • PARTITION BY 字段名(可选):按指定字段分区,相当于 "分组",不写则整个表为一个窗口
  • ORDER BY 字段名 ASC|DESC(可选):窗口内排序,影响排名、累计等计算逻辑

语法 2:定义公共窗口名(适合多函数复用)

sql 复制代码
多行函数 OVER 窗口名 ... WINDOW 窗口名 AS 
([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
  • WINDOW 窗口名 AS (...):定义一个可复用的窗口规则,给它起个名字
  • 后面的窗口函数直接用 OVER 窗口名 引用这个规则,不用重复写

示例:

sql 复制代码
SELECT id, NAME, price, category, AVG(price) OVER () FROM goods;
写法 特点 结果行数
SELECT AVG(price) FROM goods; 普通聚合函数,会把所有行合并成一行,只返回平均值 1 行
SELECT AVG(price) OVER () FROM goods; 窗口聚合函数,不合并行,每一行都保留原始数据,同时附带平均值 和原表行数相同(12 行)

写法的核心优势,是能在保留原始数据的同时,拿到全局 / 分组的统计值,方便后续做对比分析。

sql 复制代码
SELECT 
  id, name, price, category,
  AVG(price) OVER (PARTITION BY category) AS 类别均价
FROM goods;
  • 普通 GROUP BY:分组后,多行合并成一行(聚合结果)
  • PARTITION BY:分组后,不合并行,只是给每个组的行打上标记,窗口函数在每个组内单独计算,结果还是保留所有行

ROW_NUMBER()

分类并按价格从高到低排序号,显示每组的前三名

sql 复制代码
SELECT 
  ROW_NUMBER() OVER(ORDER BY price) AS num,
  id, category, NAME, price 
FROM goods WHERE row_num <= 3;

PERCENT_RANK()

它是分布类窗口函数,用来计算当前行在窗口内的百分比排名

PERCENT_RANK() = (当前行的RANK值 - 1) / (分区总行数 - 1)

sql 复制代码
SELECT 
  RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS r,
  PERCENT_RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS pr,
  id, category_id, category, name, price, stock 
FROM goods 
WHERE category_id = 1;
sql 复制代码
SELECT 
  RANK() OVER w AS r,
  PERCENT_RANK() OVER w AS pr,
  id, category_id, category, name, price, stock 
FROM goods 
WHERE category_id = 1 
WINDOW w AS (PARTITION BY category_id ORDER BY price DESC);

综合练习

等更新

相关推荐
伊甸31 小时前
Neo4j 常用语法速查(Cypher)
java·数据库·neo4j
通往曙光的路上1 小时前
JUCJUCJUC
java·前端·数据库
问心无愧05131 小时前
ctf show web入门54
前端·笔记
小陈phd1 小时前
多模态大模型学习笔记(三十九)——生成式与Transformer式OCR:从“像素抄录“到“文档智能“的完整演进
笔记·学习·transformer
泡泡以安1 小时前
Unidbg学习笔记(一):为什么需要用户态模拟器
笔记·学习
暖馒1 小时前
WPF绑定由简到繁深入笔记
笔记·wpf
shizhan_cloud1 小时前
MySQL 备份与恢复
数据库·mysql
思麟呀2 小时前
MySQL的内置函数
数据库·mysql
熏鱼的小迷弟Liu2 小时前
【MySQL】MySQL中的MVCC是什么?它与隔离级别有什么关系?
mysql·mvcc