MySQL8新特性 -- pd的MySQL笔记
文章目录
-
- [MySQL8新特性 -- pd的MySQL笔记](#MySQL8新特性 -- pd的MySQL笔记)
- 事务性数据字典
- 默认字符集和排序规则
- 窗口函数
- 通用表表达式
- 正则表达式函数
MySQL8是MySQL数据库的一个重要版本,于2018年4月19日正式发布,由Oracle公司主导开发。它是MySQL5.7(2015年发布)之后的一个重大升级版本跳过了6.x和7.x版本号,标志着架构、功能和性能的全面革新。
事务性数据字典
MySQL8 的事务性数据字典是相比 MySQL 5.7 的核心改进之一,彻底改变了元数据(如表结构、视图、索引等)的存储和管理方式。
数据库的元数据包含表结构、视图、索引等等数据,在MySQL 5.7之前,元数据存储在文件系统中,这样的存储方式会导致DDL操作不支持事务,即无法做到批量的DDL操作要么完全执行,要么完全不执行。
示例:5.7没有事务,即使没有t2表 也会删除t1表
sql
create database testdb;
use testdb;
create tabel t1(c1 int);
show tables;
drop table t1,t2;
如果是mysql8 那么最后的删除都会失败,t1仍然存在
而MySQL8之后,采用了InnoDB表的方式存储元数据,这种方式支持事务处理,设计目标是为处理大容量数据发挥最大化性能。所以MySQL8的DDL操作是支持事务的。我们再使用MySQL8执行同样的操作:
默认字符集和排序规则
MySQL 8.0 将默认字符集从 MySQL 5.7 的 latin1 更改为 utf8mb4 ,并将默认排序规则从 latin1_swedish_ci 更改为 utf8mb4_0900_ai_ci 。这一变化显著提升了字符集支持的范围和排序精度。
utf8mb4 是 MySQL 对 UTF-8 的实现,支持完整的 Unicode 字符集,适合存储多语言文本、Emoji 和特殊符号。包括多国语言和补充语言,如 Emoji 和罕见汉字。而 latin1 仅支持 256 个字符,无法存储 Emoji 或汉字。
而uft8的排序规则 utf8mb4_0900_ai_ci 基于 Unicode 9.0 标准,取代了 MySQL 5.7 中的uft8排序规则utf8_general_ci ,提供更精确的 Unicode 排序,比较和排序更符合语言习惯(如中文拼音、笔画排序)。
sql
create table test_emoji(
id int primary key,
content varchar(100) character set utf8mb4 collate utf8mb4_0900_ai_ci
);
insert into test_emoji (id,content) value (1, '😊 你好');
select content from test_emoji where id =1;
窗口函数
窗口函数也叫分析函数,用于将查询的结果进行较为复杂的统计分析,MySQL从8.0开始支持窗口函数。
例如有个销售流水数据表,想统计今年截至当前的累计销售额,之前的mysql较难实现

窗口函数不能过滤查询结果,只能对查询结果进行分析并输出。所以窗口函数是写在SELECT之后,而不是写在WHERE之后。即:
sql
SELECT 窗口函数 WHERE 条件;
而窗口函数的基本格式如下:
sql
函数名(字段) over(
partition by <要分组的字段>
order by <排序的字段>
rows <窗口范围>
)
如果over后面括号中什么都不写,则意味着窗口包含所有的查询结果
示例:查询每个人的销售额,并统计每个月末,每个人的销售总额(假设这个是表每一行代表一个月的销售额)
sql
select *,
sum(s.sales_volume) over(
partition by name -- 根据名字分组
order by YEAR(s.statistical_date), MONTH(s.statistical_date) -- 根据月份排序
rows between unbounded preceding and current row -- 窗口范围 从之前的所有行到当前行
) cum_volume -- 设置别名
from sales s;
接下来我们具体介绍一下rows子句的关键字和写法:
- preceding:前面行
- following:后面行
- current row:当前行
- unbounded:所有行
例如:
- rows between 2 preceding and current row :窗口取前面两行到当前行(取近三月的数据)
- rows between unbounded preceding and current row :窗口取之前所有行到当前行
- rows between current row and unbounded following: 窗口当前行到之后所有行
- rows between 3 preceding and 1 following:窗口取当前行的前面三行到后面一行,总共五行
如果子句中只有partition by没有order by和rows,窗口规范默认是所有行
如果子句中有order by没有rows,窗口规范默认是取之前所有行到当前行
聚合函数
窗口函数中的聚合函数,负责在窗口内执行聚合计算,支持窗口内累计或统计。常见的聚合函数有这几种:
- SUM():计算窗口内值的总和
- AVG() :计算窗口内值的平均值
- COUNT():计算窗口内的行数
- MIN() :找出窗口内的最小值
- MAX():找出窗口内的最大值
接下来我们用查询语句举例聚合函数的使用:
sql
-- 计算每个销售人员的累计销售额
select *,
sum(sales_volumn) over(
partition by name
order by statistical_date
) cum_volume
from sales;
-- 计算每个部门的平均销售额
select *,
avg(sales_volumn) over(
partition by department
) dept_avg_sales
from sales;
-- 计算每个销售人员的销售记录数量
select *,
count(*) over(
parition by name
) sales_count
from sales;
-- 找出每个销售人员的最低单月销售额
select *,
min(sales_volume) over(
partition by name
) min_sales
from sales;
对比之前的聚合函数:
sql
-- 找出每个销售人员的最高单月销售额
SELECT name,SUM(sales_volume) FROM sales GROUP BY name;
- 普通场景下的聚合函数基于GROUP BY子句定义的组进行计算,将多条记录聚合为一条,只能展示分组字段和聚合函数字段;
- 窗口函数是基于窗口定义进行计算,每条记录都会执行,而且针对于不同的记录可以设置不同的窗口范围,有几条记录执行完还是几条。并且能展示所有字段
排名函数
排名函数可以为数据集中的行分配排名或序号,常用于排序和比较场景。常见的排名函数有这几种:
- ROW_NUMBER():为结果集中的每一行分配唯一的连续序号(1, 2, 3...),即使值相同也会分配不同序号
- RANK():为行分配排名,相同值获得相同排名,但会跳过后续排名(如1,1,3)
- DENSE_RANK():类似RANK()但不跳过排名数字(如1,1,2)
示例: 计算3月销售额排名
sql
select *,
row_number() over(order by sales_volume desc) as row_num,
rank() over(order by sales_volume desc) as ran,
dense_rank() over(order by sales_volume desc) as den_ran
from sales
where month(statistical_date) = 3
结合窗口函数与普通聚合函数, 计算全年销售总额排名
sql
SELECT user_id,username,
SUM(sales_volume) as total_sales,
ROW_NUMBER() OVER(ORDER BY SUM(sales_volume) DESC) as row_num,
RANK() OVER(ORDER BY SUM(sales_volume) DESC) as ran,
DENSE_RANK() OVER(ORDER BY SUM(sales_volume) DESC) as den_ran
FROM sales
WHERE YEAR(statistical_date) = YEAR(CURDATE()) -- 或指定年份如 2026
GROUP BY user_id
ORDER BY total_sales DESC;
- 使用 YEAR(statistical_date) = YEAR(CURDATE())筛选当前年份数据
- 使用 GROUP BY user_id按用户汇总
- 使用 SUM(sales_volume)计算全年总销售额
- 窗口函数基于汇总后的总销售额进行排名
分析函数
分析函数用于访问窗口内其他行的数据。常见的分析函数有这几种:
- LAG(column, n, default):返回当前行前 n 行的 column 列的值,若没有则返回当前行中default列的值。
- LEAD(column, n, default):返回当前行后 n 行的 column 值,若没有则返回当前行中default列的值。
- FIRST_VALUE(column):返回窗口内第一行的 column 值。
- LAST_VALUE(column):返回窗口内最后一行的 column 值。
- NTH_VALUE(column, n):返回窗口内第 n 行的 column 值。
示例:
sql
select
*,
-- 显示每个人上个月的销售额
LAG(sales_volume, 1) over(
partition by name
order by statistical_date
rows between unbounded preceding and unbounded following
) as prev_month_sales,
-- 显示每个销售人员下个月的销售额
LEAD(sales_volume, 1) over(
partition by name
order by statistical_date
rows between unbounded preceding and unbounded following
) as next_month_sales,
-- 显示每个销售人员第一个月的销售额
FIRST_VALUE(sales_volume) over(
partition by name
order by statistical_date
rows between unbounded preceding and unbounded following
) as first_month_sales,
-- 显示每个销售人员第二个月的销售额
NTH_VALUE(sales_volume, 2) over(
partition by name
order by statistical_date
rows between unbounded preceding and unbounded following
) as second_month_sales,
-- 显示每个销售人员最后一个月的销售额
LAST_VALUE(sales_volume) over(
partition by name
order by statistical_date
rows between unbounded preceding and unbounded following
) as last_month_sales,
from sales;
分布函数
分布函数计算行在窗口内的相对位置或分布。
- CUME_DIST():返回当前行值在窗口内的累积分布(0 < 值 ≤ 1)。
- PERCENT_RANK():返回当前行在窗口内的百分比排名(0 ≤ 值 < 1)。
示例:计算1月每个销售人员销售额的百分比排名
sql
select
*,
percent_rank() over(order by sales_volume desc) as pr,
cume_dist() over(order by sales_volume desc) as cd
from sales
where month(statistical_date) =1;
计算全年每个销售人员的销售额百分比排名
sql
SELECT
name,
SUM(sales_volume) as total_sales,
PERCENT_RANK() OVER(ORDER BY SUM(sales_volume) DESC) as pr,
CUME_DIST() OVER(ORDER BY SUM(sales_volume) DESC) as cd
FROM sales
WHERE YEAR(statistical_date) = YEAR(CURDATE()) -- 或指定年份如 2026
GROUP BY name
ORDER BY total_sales DESC;
通用表表达式
通用表表达式(Common Table Expressions, CTE)是MySQL 8.0引入的一项重要功能,它极大地增强了SQL查询的可读性和灵活性。
CTE是一个临时的结果集,只在查询执行期间存在,它可以看作是一个临时表,我们经常使用CTE代替复杂的子查询语句,增强SQL的可读性。基本语法为:
sql
WITH cte_name AS (
SELECT ... -- CTE查询定义
)
SELECT * FROM cte_name; -- 主查询
示例:找出每月销售额超过该部门当月平均销售额的销售人员
sql
-- 子查询
select *
from sales s
join(
select
department,
month(statistical_date) as Month,
avg(sales_volume) as avg_sales,
from sales
group by department, month(statistical_date)
) as dept_avg
on s.department = dept_avg.department and
month(s.statistical_date) = dept_avg.Month
where s.sales_volume > dept_avg.avg_sales;
-- CTE查询形式
with dept_avg as(
select
department,
month(statistical_date) as Month,
avg(sales_volume) as avg_sales,
from sales
group by department, month(statistical_date)
)
select *
from sales s
join dept_avg
on s.department = dept_avg.department and
month(s.statistical_date) = dept_avg.Month
where s.sales_volume > dept_avg.avg_sales;
CTE的优势:
- 分步清晰:将复杂查询分解为逻辑步骤
- 可读性强:每个CTE块有明确的业务含义
- 易于维护:修改一个步骤不影响其他部分
- 避免重复:每个子查询只需定义一次
- 调试方便:可以单独测试每个CTE块
示例: 输出每个月每个人的销售额,以及该部门该月的平均销售额,以及每个人的平均销售额
sql
with
dept_avg as(
select
department,
month(statistical_date) as Month,
avg(sales_volume) as avg_sales,
from sales
group by department, month(statistical_date)
),
emp_avg as(
select
name,
avg(sales_volume) as avg_sales,
from sales
group by name
)
select
sales.*,
dept_avg.avg_sales as dept_avg_sales,
emp_avg.avg_sales as emp_avg_sales
from sales
left join dept_avg
on sales.department = dept_avg.department and month(sales.statistical_date) = dept_avg.Month
left join emp_avg
on sales.name = emp_avg.name
正则表达式函数
- REGEXP_LIKE(expr, pattern, [match_type]):检查字符串是否匹配正则表达式。
- expr:要检查的字符串(列或常量)。
- pattern:正则表达式。
- match_type(可选):匹配选项
- c:大小写敏感(默认)。
- i:忽略大小写。
- REGEXP_REPLACE(expr, pattern, replacement):替换匹配正则表达式的子字符串,将字符串 expr 中匹配 pattern 的部分替换为 replacement。
- REGEXP_SUBSTR(expr, pattern):提取 expr 中匹配 pattern 的子字符串。
- REGEXP_INSTR(expr, pattern):返回 pattern 在 expr 中匹配的起始位置。
sql
-- 筛选有效的邮箱地址
select *
from contacts
where regexp_like(email,'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$','i');
-- 将电话号码替换为同一格式
select *
REGEXP_REPLACE(phone,'[^0-9]','');
from contacts
^[0-9]表示匹配以数字开头的字符串
\^0-9\]匹配任意单个字符,只要不是0-9