MySQL8 的新特性:NoSQL、索引、账户管理、窗口函数...

一、新特性概述

1.1 更简便的 NoSQL 支持

b600-mysql-8-mds.en.jpg

NoSQL 泛指非关系型数据库和数据存储。随着互联网平台的规模飞速发展,传统的关系型数据库已经越来越不能满足需求。

从 5.6 版本开始,MySQL 就开始支持简单的 NoSQL 存储功能。MySQL8 对这一功能做了优化,以更灵活的方式实现 NosQL 功能,不再依赖模式(schema)。

1.2 更好的索引

b600-mysql-8-2x.en.jpg

在查询中,正确地使用索引可以提高查询的效率,MySQL8 中新增了隐藏索引降序索引,隐藏索引可以用来测试去掉索引对查询性能的影响。在查询中混合存在多列索引时,使用降序索引可以提高查询的性能。

1.3 更完善的 JSON 支持

MySQL 从 5.7 开始支持原生 JSON 数据的存储,MySQL8 对这一功能做了优化,增加了聚合函数JSON_ARRAYAGG()JSON_OBJECTAGG(),将参数聚合为 JSON 数组或对象,新增了行内操作符 ->> ,是列路径运算符 -> 的增强,对 JSON 排序做了提升,并优化了 JSON 的更新操作。

1.4 安全和账户管理

MySQL8 中新增了 caching_sha2_password 授权插件、角色、密码历史记录和 FIPS 模式支持,这些特性提高了数据库的安全性和性能,使数据库管理员能够更灵活地进行账户管理工作。

1.5 InnoDB 的变化

InnoDB 是 MySQL 默认的存储引擎,是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键。在 MySQL8 版本中,InnoDB 在自增、索引、加密、死锁、共享锁等方面做了大量的改进和优化,并且支持原子数据定义语言(DDL),提高了数据安全性,对事务提供更好的支持。

1.6 数据字典

在之前的 MySQL 版本中,字典数据都存储在元数据文件和非事务表中。从 MySQL8 开始新增了事务数据字典,在这个字典里存储着数据库对象信息,这些数据字典存储在内部事务表中。

1.7 原子数据定义语句

MySQL8 开始支持原子数据定义语句(Automic DDL),即原子 DDL。目前,只有 InnoDB 存储引擎支持原子 DDL。原子数据定义语句(DDL)将与 DDL 操作相关的数据字典更新、存储引擎操作、二进制日志写入结合到一个单独的原子事务中,这使得即使服务器崩溃,事务也会提交或回滚。

使用支持原子操作的存储引擎所创建的表,在执行 DROP TABLECREATE TABLEALTER TABLERENAME TABLETRUNCATE TABLECREATE TABLESPACEDROP TABLESPACE 等操作时,都支持原子操作,即事务要么完全操作成功,要么失败后回滚,不再进行部分提交。

对于从 MySQL5.7 复制到 MySQL8 版本中的语句,可以添加 IF EXISTSIF NOT EXISTS 语句来避免发生错误。

1.8 资源管理

MySQL8 开始支持创建和管理资源组,玩许将服务器内运行的线程分配给特定的分组,以便线程根据组内可用资源执行。组属性能够控制组内资源,启用或限制组内资源消耗。数据库管理员能够根据不同的工作负载适当地更改这些属性。

目前,CPU 时间是可控资源,由 “虚拟CPU” 这个概念来表示,此术语包含 CPU 的核心数,超线程,硬件线程等等。服务器在启动时确定可用的虚拟CPU数量。拥有对应权限的数据库管理员可以将这些CPU 与资源组关联,并为资源组分配线程。

资源组组件为 MySQL 中的资源组管理提供了 SQL 接口。资源组的属性用于定义资源组。MySQL 中存在两个默认组,系统组和用户组,默认的组不能被删除,其属性也不能被更改。对于用户自定义的组,资源组创建时可初始化所有的属性,除去名字和类型,其他属性都可在创建之后进行更改。

在一些平台下,或进行了某些 MySQL 的配置时,资源管理的功能将受到限制,甚至不可用。例如,如果安装了线程池插件,或者使用的是 macOS 系统,资源管理将处于不可用状态。在 FreeBSD 和 Solaris 系统中,资源线程优先级将失效。在 LinuX 系统中,只有配置了 CAP_SYS_NICE 属性,资源管理优先级才能发挥作用。

1.9 字符集支持

b600-mysql-8-utf8mb4.en.jpg

MySQL8 中默认的字符集由 latin1 更改为 utf8mb4,并首次增加了日语所特定使用的集合, utf8mb4_ja_0900_as_cs

1.10 优化器增强

MySQL 优化器开始支持隐藏索引降序索引隐藏索引不会被优化器使用,验证索引的必要性时不需要删除索引,先将索引隐藏,如果优化器性能无影响就可以真正地删除索引。降序索引允许优化器对多个列进行排序,并且允许排序顺序不一致。

1.11 公用表表达式

公用表表达式(Common Table Expressions)简称为 CTE,MySQL 现在支持递归和非递归两种形式的 CTE。CTE 通过在 SELECT 语句或其他特定语句前使用 WITH 语句对临时结果集进行命名。 基础语法如下:

1
2
WITH cte_name (col_name1,clo_name2..) AS (Suquery)
SELECT * FROM cte_name;

Subquery 代表子查询,子查询前使用 WITH 语句将结果集命名为 cte_name,在后续的查询中即可使用 cte_name 进行查询。

1.12 窗口函数

MySQL8 开始支持窗口函数。在之前的版本中已存在的大部分聚合函数在 MySQL8 中也可以作为窗口函数来使用。

1.13 正则表达式支持

MySQL 在 8.0.4 以后的版本中采用支持 Unicode 的国际化组件库实现正则表达式操作,这种方式不仅能提供完全的 Unicode 支持,而且是多字节安全编码。

MySQL 增加了 REGEXP_LIKE()EGEXP_INSTR()REGEXP_REPLACE()REGEXP_SUBSTRO() 等函数来提升性能。另外,regexp_stack_limitregexp_time_limit 系统变量能够通过匹配引擎来控制资源消耗。

1.14 内部临时表

empTable 存储引擎取代 MEMORY 存储引擎成为内部临时表的默认存储引擎。TempTable 存储引擎为 VARCHARVARBINARY 列提供高效存储。

internal_tmp_mem_storage_engine 会话变量定义了内部临时表的存储引擎,可选的值有两个,TempTableMEMORY ,其中 TempTable 为默认的存储引擎。temptable_max_ram 系统配置项定义了 TempTable 存储引擎可使用的最大内存数量。

1.15 日志记录

在 MySQL8 中错误日志子系统由一系列 MySQL 组件构成。这些组件的构成由系统变量 log_error_services 来配置,能够实现日志事件的过滤和写入。

1.16 备份锁

新的备份锁允许在线备份期间执行数据操作语句,同时阻止可能造成快照不一致的操作。

新备份锁由 LOCK INSTANCE FOR BACKUPUNLOCK INSTANCE 语法提供支持,执行这些操作需要备份管理员特权。

1.7 增强的 mysql 复制

MySQL8 复制支持对 JSON 文档进行部分更新的二进制日志记录,该记录使用紧凑的二进制格式,从而节省记录完整 JSON 文档的空间。

当使用基于语句的日志记录时,这种紧凑的日志记录会自动完成,并且可以通过将新的 binlog_row_value_options 系统变量值设置为 PARTIAL_JSON 来启用。

b600-mysql-database-service5.en.jpg

二、窗口函数

2.1 概念

MySQL 从 8.0 版本开始支持窗口函数。窗口函数的作用类似于在查询中对数据进行分组,不同的是,分组操作会把分组的结果聚合成一条记录,而窗口函数是将结果置于每一条数据记录中。

2.2 分类

  • 静态窗口函数:静态窗口函数的窗口大小是固定的,不会因为记录的不同而不同;
  • 动态窗口函数:动态窗口函数的窗口大小会随着记录的不同而变化。

2.3 语法结构

1
2
3
4
#方式一
函数 OVER ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
#方式二
函数 OVER 窗口名 ... WINDOW 窗口名 AS ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])

2.4 举例

2.4.1 准备表与数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE goods (
	id INT PRIMARY KEY AUTO_INCREMENT,
	category_id INT,
	category VARCHAR ( 15 ),
	NAME VARCHAR ( 30 ),
	price DECIMAL ( 10, 2 ),
	stock INT,
	upper_time DATETIME 
);
	
INSERT INTO goods ( category_id, category, NAME, price, stock, upper_time )
VALUES
	( 1, '女装/女士精品', 'T恤', 39.90, 1000, '2020-11-10 00:00:00' ),
	( 1, '女装/女士精品 ', '连衣裙', 79.90, 2500, '2020-11-10 00:00:00' ),
	( 1, '女装/女士精品', '卫衣', 89.90, 1500, ' 2020-11-10 00:00:00 ' ),
	( 1, '女装/女士精品', '牛仔裤', 89.90, 3500, '2020-11-10 00:00:00' ),
	( 1, '女装/女士精品','百褶裙',29.90,500,' 2020-11-10 00:00:00' ),
	( 1, '女装/女士精品', '呢绒外套', 399.90, 1200, '2020-11-10 00:00:00' ),
	( 2, '户外运动', '自行车', 399.90, 1000, '2020-11-10 00:00:00 ' ),
	( 2, '户外运动', '山地自行车', 1399.90, 2500, '2020-11-10 00:00:00' ),
	( 2, '户外运动', '登山杖', 59.90, 1500, '2020-11-10 00:00:00' ),
	( 2, '户外运动', '骑行装备', 399.90, 3500, '2020-11-10 00:00:00' ),
	( 2, '户外运动', '运动外套', 799.90, 500, ' 2020-11-10 00:00:00 ' ),
	( 2, '户外运动', '滑板', 499.90, 1200, ' 2020-11-10 00:00:00' );	

2.4.2 排序函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#ROW NUMBER()函数:按PARTITION来给每个区添加顺序排序
#举例:查询goods数据表中每个商品分类下价格降序排列的各个商品信息
SELECT
	ROW_NUMBER() OVER ( PARTITION BY category_id ORDER BY price DESC ) AS row_num,
	id,
	category_id,
	category,
	NAME,
	price,
	stock
FROM
	goods;
	
#举例:查询goods数据表中每个商品分类下价格最高的3种商品信息
SELECT
	* 
FROM
	(
	SELECT
		ROW_NUMBER() OVER ( PARTITION BY category_id ORDER BY price DESC ) AS row_num,
		id,
		category_id,
		category,
		NAME,
		price,
		stock 
	FROM
		goods 
	) t 
WHERE
	row_num <= 3;
	
#RANK()函数:按PARTITION来给每个区添加名次排序(并列第三就是1,2,3,3,5
#举例:使用RANK()函数获取goods数据表中各类别的价格从高到低排序的各商品信息
SELECT
	RANK() OVER ( PARTITION BY category_id ORDER BY price DESC ) AS row_num,
	id,
	category_id,
	category,
	NAME,
	price,
	stock
FROM
	goods;

# DENSE_RANK()函数:按PARTITION来给每个区添加排序(并列第三就是1,2,3,3,4
SELECT
	DENSE_RANK() OVER ( PARTITION BY category_id ORDER BY price DESC ) AS row_num,
	id,
	category_id,
	category,
	NAME,
	price,
	stock 
FROM
	goods;

2.4.3 分布函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#分布函数
#PERCENT_RANK()函数:公式(rank -1)/(rows - 1)
#举例:计算goods数据表中名称为“女装/女士精品"的类别下的商品的PERCENT_RANK值。
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;
	
	
#CUME_DIST()函数:当前价格/区的最大价格
#举例:查询goods数据表中小于或等于当前价格的比例。
SELECT
	CUME_DIST() OVER ( PARTITION BY category_id ORDER BY price ASC ) AS cd,
	id,
	category,
	NAME,
	price 
FROM
	goods;

2.4.4 分布函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#LAG(expr,n)函数:返回当前行的前n行的expr值
#举例:查询goods数据表中前一个商品价格与当前商品价格的差值
SELECT
	id,
	category,
	NAME,
	price,
	pre_price,
	price - pre_price AS diff_price 
FROM
	(
	SELECT
		id,
		category,
		NAME,
		price,
		LAG( price, 1 ) OVER ( PARTITION BY category_id ORDER BY price ASC ) AS pre_price 
	FROM
	goods WINDOW w AS ( PARTITION BY category_id ORDER BY price )) t;

#其中,子查询是查询当前行数据 + 上一行价格
SELECT
	id,
	category,
	NAME,
	price,
	LAG( price, 1 ) OVER ( PARTITION BY category_id ORDER BY price ASC) AS pre_price
FROM
	goods

#LEAD(expr,n)函数:返回当前行的后n行的expr值
#举例:查询goods数据表中后一个商品价格与当前商品价格的差值
SELECT
	id,
	category,
	NAME,
	price,
	behind_price,
	price - behind_price AS diff_price 
FROM
	(
	SELECT
		id,
		category,
		NAME,
		price,
		LEAD( price, 1 ) OVER ( PARTITION BY category_id ORDER BY price ASC ) AS behind_price 
	FROM
	goods WINDOW w AS ( PARTITION BY category_id ORDER BY price )) t;

2.4.4 首尾函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#FIRST_VALUE(expr)函数:返回第一个expr的值
#举例:按照价格排序,查询第1个商品的价格信息。
SELECT
	id,
	category,
	NAME,
	price,
	stock,
	FIRST_VALUE( price ) OVER w AS first_price 
FROM
	goods WINDOW w AS ( PARTITION BY category_id ORDER BY price );
	
#LAST_VALUE(expr)函数:返回最一个expr的值

2.5 常用窗口函数

  • 排名函数:row_number()rank()dense_rank()
  • 聚合函数:max()min()count()sum()avg()median()
  • 向前向后取值:lag()lead()
  • 百分位:percent_rank()
  • 取值函数:first_value()last_value()nth_value()
  • 分箱函数:ntile()

2.6 小结

窗口函数的特点是可以分组,而且可以在分组内排序。

另外,窗口函数不会因为分组而减少原表中的行数,这对我们在原表数据的基础上进行统计和排序非常有用。

三、公用表表达式

公用表表达式(或通用表表达式)简称为 CTE(Common Table Expressions)。

CTE 是一个命名的临时结果集,作用范围是当前语句,CTE 可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的,CTE 可以引用其他 CTE,但子查询不能引用其他子查询。所以,可以考虑代替子查询。

依据语法结构和执行方式的不同,公用表表达式分为普通公用表表达式递归公用表表达式两种。

3.1 普通共用表达式

3.1.1 语法

1
2
3
WITH CTE名称
AS (子查询)
SELECT|DELETE|UPDATE 语句

3.1.2 举例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#子查询语句
SELECT
	* 
FROM
	departments 
WHERE
	department_id IN ( SELECT DISTINCT department_id FROM employees );
	
#普通公用表达式
WITH department_id_socpe AS ( SELECT DISTINCT department_id FROM employees )
SELECT
* 
FROM
	departments d
	JOIN department_id_socpe d_s 
WHERE
	d.department_id = d_s.department_id;

3.2 递归公用表达式

3.2.1 语法

1
2
3
WITH RECURSIVE
CTE名称 AS (子查询)
SELECT|DELETE|UPDATE 语句

3.2.2 举例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#递归公用表达式
WITH RECURSIVE cte AS (
	SELECT
		employee_id,
		last_name,
		manager_id,
		1 AS n 
	FROM
		employees 
	WHERE
		employee_id = 100 -- 种子查询,找到他的下下属
	UNION ALL
	SELECT
		a.employee_id,
		a.last_name,
		a.manager_id,
		n + 1 
	FROM
		employees AS a
		JOIN cte ON ( a.manager_id = cte.employee_id ) -- 递归查询,找出以递归公用表达式的人为领导的人
	) 
SELECT
	employee_id,
	last_name 
FROM
	cte 
WHERE
	n >= 3;

3.2.3 小结

公用表表达式的作用是可以替代子查询,而且可以被多次引用。递归公用表表达式对查询有一个共同根节点的树形结构数据非常高效,可以轻松搞定其他查询方式难以处理的查询。

————————————————

版权声明:本文为CSDN博主「不入开发不工作」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_46245201/article/details/123002895