MySQL 8.0 新特性

一、账号与安全

1、用户创建于授权

MySQL 8.0 创建用户和用户授权命令需要分开执行,MySQL 5.7之前直接直接使用 grant 命令实现两步操作。

[5.7.37-log]>grant all privileges on *.* to lzy@'%' identified by '123456';
Query OK, 0 rows affected, 1 warning (0.00 sec)

[8.0.27]>grant all privileges on *.* to lzy@'%' identified by '123456';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'identified by '123456'' at line 1

[8.0.27]>create user lzy@'%' identified by '123456';
Query OK, 0 rows affected (0.03 sec)

[8.0.27]>grant all privileges on *.* to lzy@'%';
Query OK, 0 rows affected (0.02 sec)

[8.0.27]>flush privileges;
Query OK, 0 rows affected (0.01 s

2、认证插件更新

MySQL 5.7 的插件为:mysql_native_password

MySQL 8.0 认证插件更新为:caching_sha2_password

 

插件升级后,破解密码难度增大,安全性大大增强,但是,在远程访问时,需要指定目标服务器mysql的RSA公钥,否则,使用明文密码进行访问,会报错。可以想象到,在远程管理大量的服务器时,这明显会降低办事效率,所以目前还是建议使用mysql_native_password插件。

远程明文访问报错信息:Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection

[5.7.37-log]>show variables like 'default_authentication_plugin';
+-------------------------------+-----------------------+
| Variable_name                 | Value                 |
+-------------------------------+-----------------------+
| default_authentication_plugin | mysql_native_password |
+-------------------------------+-----------------------+
1 row in set (0.00 sec)


[5.7.37-log]>select user,host,plugin from mysql.user;
+---------------+-----------+-----------------------+
| user          | host      | plugin                |
+---------------+-----------+-----------------------+
| root          | localhost | mysql_native_password |
| mysql.session | localhost | mysql_native_password |
| mysql.sys     | localhost | mysql_native_password |
| lzy           | %         | mysql_native_password |
+---------------+-----------+-----------------------+
4 rows in set (0.00 sec

[8.0.27]>show variables like 'default_authentication_plugin';
+-------------------------------+-----------------------+
| Variable_name                 | Value                 |
+-------------------------------+-----------------------+
| default_authentication_plugin | caching_sha2_password |
+-------------------------------+-----------------------+
1 row in set (0.03 sec)


--指定插件
[8.0.27]>create user test_user@'%' identified with mysql_native_password by '123456';
Query OK, 0 rows affected (0.02 sec)


[8.0.27]>select user,host,plugin from mysql.user;
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| lzy              | %         | caching_sha2_password |
| test_user        | %         | mysql_native_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+
6 rows in set (0.00 se

3、密码重用策略

新增3个参数用于控制密码重复使用策略

  • password_history:限制修改的密码必须是多少次内不重复,修改的密码信息会记录在mysql.password_history中,修改密码时会与表中的信息对比,默认值为0,即不限制。
[8.0.27]>set persist password_history = 2;
Query OK, 0 rows affected (0.00 sec)


[8.0.27]>alter user test_user@'%' identified by '123456';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>alter user test_user@'%' identified by '123456';
ERROR 3638 (HY000): Cannot use these credentials for 'test_user@%' because they contradict the password history policy


[8.0.27]>alter user test_user@'%' identified by '123457';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>alter user test_user@'%' identified by '123458';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>alter user test_user@'%' identified by '123456';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>select * from mysql.password_history;
+------+-----------+----------------------------+-------------------------------------------+
| Host | User      | Password_timestamp         | Password                                  |
+------+-----------+----------------------------+-------------------------------------------+
| %    | test_user | 2022-04-25 15:23:15.012121 | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| %    | test_user | 2022-04-25 15:23:10.886168 | *2727EA229A9F2F2D696375270574BF7E658136DD |
+------+-----------+----------------------------+-------------------------------------------+
2 rows in set (0.0

  • password_reuse_interval:密码重用间隔时间,单位:天;默认值为0,即不限制。
  • password_require_current:修改密码时必须要提供当前密码,默认关闭;超级用户进行操作时不受限制。
[8.0.27]>set persist password_require_current = 1;
Query OK, 0 rows affected (0.00 sec)


[8.0.27]>select user();
+---------------------+
| user()              |
+---------------------+
| test_user@localhost |
+---------------------+
1 row in set (0.00 sec)


[8.0.27]>alter user test_user@'%' identified by '123451';
ERROR 3892 (HY000): Current password needs to be specified in the REPLACE clause in order to change it.


[8.0.27]>alter user test_user@'%' identified by '123451' replace '123456';
Query OK, 0 rows affected (0.00 s

4、新增角色管理

[8.0.27]>create role dba;
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>select host,user from mysql.user;
+-----------+------------------+
| host      | user             |
+-----------+------------------+
| %         | dba              |
| %         | jack             |
| %         | lzy              |
| %         | test_user        |
| localhost | mysql.infoschema |
| localhost | mysql.session    |
| localhost | mysql.sys        |
| localhost | root             |
+-----------+------------------+
8 rows in set (0.01 sec)


[8.0.27]>grant all privileges on test.* to dba;
Query OK, 0 rows affected (0.02 sec)


[8.0.27]>show grants for dba;
+-----------------------------------------------+
| Grants for dba@%                              |
+-----------------------------------------------+
| GRANT USAGE ON *.* TO `dba`@`%`               |
| GRANT ALL PRIVILEGES ON `test`.* TO `dba`@`%` |
+-----------------------------------------------+
2 rows in set (0.00 sec)


[8.0.27]>create user jack@'%' identified by '123456';
Query OK, 0 rows affected (0.03 sec)


[8.0.27]>grant dba to jack@'%';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>show grants for jack@'%';
+----------------------------------+
| Grants for jack@%                |
+----------------------------------+
| GRANT USAGE ON *.* TO `jack`@`%` |
| GRANT `dba`@`%` TO `jack`@`%`    |
+----------------------------------+
2 rows in set (0.00 sec)


[8.0.27]>show grants for jack@'%' using dba;
+------------------------------------------------+
| Grants for jack@%                              |
+------------------------------------------------+
| GRANT USAGE ON *.* TO `jack`@`%`               |
| GRANT ALL PRIVILEGES ON `test`.* TO `jack`@`%` |
| GRANT `dba`@`%` TO `jack`@`%`                  |
+------------------------------------------------+
3 rows in set (0.00 sec)


[8.0.27]>set default role all to jack@'%';
Query OK, 0 rows affected (0.02 sec)


[8.0.27]>select * from mysql.default_roles;
+------+------+-------------------+-------------------+
| HOST | USER | DEFAULT_ROLE_HOST | DEFAULT_ROLE_USER |
+------+------+-------------------+-------------------+
| %    | jack | %                 | dba               |
+------+------+-------------------+-------------------+
1 row in set (0.00 sec)


[8.0.27]>select * from mysql.role_edges;
+-----------+-----------+---------+---------+-------------------+
| FROM_HOST | FROM_USER | TO_HOST | TO_USER | WITH_ADMIN_OPTION |
+-----------+-----------+---------+---------+-------------------+
| %         | dba       | %       | jack    | N                 |
+-----------+-----------+---------+---------+-------------------+
1 row in set 

不给用户设置默认角色时,访问对应数据库会报错。
[8.0.27]>select user();
+----------------+
| user()         |
+----------------+
| jack@localhost |
+----------------+
1 row in set (0.00 sec)


[8.0.27]>use test;
ERROR 1044 (42000): Access denied for user 'jack'@'%' to database 'test'


[8.0.27]>select current_role();
+----------------+
| current_role() |
+----------------+
| NONE           |
+----------------+
1 row in set (0.00 sec)


[8.0.27]>set role dba;
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>use test;
Database changed


[8.0.27]>select * from test.tb;
Empty set (0.00

二、索引

1、隐藏索引

隐藏索引只对优化器隐藏,而且仍需维护成本;主键不可设置为隐藏;

使用场景:灰度发布

[8.0.27]>alter table tb add index idx_a(a) invisible;
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0


[8.0.27]>show index from tb\G
*************************** 1. row ***************************
        Table: tb
   Non_unique: 1
     Key_name: idx_a
 Seq_in_index: 1
  Column_name: a
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: NO
   Expression: NULL
1 row in set (0.00 sec)


[8.0.27]>explain select * from tb where a=1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)


[8.0.27]>set session optimizer_switch = "use_invisible_indexes=on";
Query OK, 0 rows affected (0.00 sec)


[8.0.27]>explain select * from tb where a=1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: ref
possible_keys: idx_a
          key: idx_a
      key_len: 5
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.01 sec)


[8.0.27]>alter table tb alter index idx_a visible;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnin

2、降序索引

  • MySQL 5.7 创建降序索引时,实际上还是按升序创建,8.0 则会创建真正的降序索引
  • MySQL 8.0 只有innodb存储引擎支持降序索引,且同时只有btree索引支持降序
  • 使用MySQL 8.0 降序索引时,还支持反向查找降序索引
  • MySQL 5.7 执行group by操作时会进行隐式排序,8.0则是随机返回
[5.7.37-log]>alter table tb add index idx_a_b(a asc,b desc);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0


[5.7.37-log]>show create table tb\G
*************************** 1. row ***************************
       Table: tb
Create Table: CREATE TABLE `tb` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  KEY `idx_a_b` (`a`,`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)




[8.0.27]>alter table tb add index idx_a_b(a asc,b desc);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0


[8.0.27]>show create table tb\G
*************************** 1. row ***************************
       Table: tb
Create Table: CREATE TABLE `tb` (
  `a` int DEFAULT NULL,
  `b` int DEFAULT NULL,
  KEY `idx_a_b` (`a`,`b` DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 

真假降序索引验证
[5.7.37-log]>insert into tb(a,b) values(1,100),(1,200),(2,300),(2,50);
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0


[5.7.37-log]>explain select * from tb order by a asc,b desc\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: index
possible_keys: NULL
          key: idx_a_b
      key_len: 10
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: Using index; Using filesort
1 row in set, 1 warning (0.00 sec)




[8.0.27]>insert into tb(a,b) values(1,100),(1,200),(2,300),(2,50);
Query OK, 4 rows affected (0.02 sec)
Records: 4  Duplicates: 0  Warnings: 0


[8.0.27]>explain select * from tb order by a asc,b desc\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: index
possible_keys: NULL
          key: idx_a_b
      key_len: 10
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: Using index
1 row in set, 1 warning (0.01 

group by是否隐式排序
[5.7.37-log]>select b,count(1) from tb group by b;
+------+----------+
| b    | count(1) |
+------+----------+
|   50 |        1 |
|  100 |        1 |
|  200 |        1 |
|  300 |        1 |
+------+----------+
4 rows in set (0.00 sec)


[5.7.37-log]>explain select b,count(1) from tb group by b\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: index
possible_keys: idx_a_b
          key: idx_a_b
      key_len: 10
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: Using index; Using temporary; Using filesort
1 row in set, 1 warning (0.00 sec)




[8.0.27]>select b,count(1) from tb group by b;
+------+----------+
| b    | count(1) |
+------+----------+
|  200 |        1 |
|  100 |        1 |
|  300 |        1 |
|   50 |        1 |
+------+----------+
4 rows in set (0.00 sec)


[8.0.27]>explain select b,count(1) from tb group by b\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: index
possible_keys: idx_a_b
          key: idx_a_b
      key_len: 10
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: Using index; Using temporary
1 row in set, 1 warning (0.01 

降序索引反向查找
[8.0.27]>explain select * from tb order by a desc,b asc\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb
   partitions: NULL
         type: index
possible_keys: NULL
          key: idx_a_b
      key_len: 10
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: Backward index scan; Using index
1 row in set, 1 warning (0.00 sec)

3、函数索引

  • 从MySQL 8.0.13版本开始支持函数,函数索引同时支持降序及json
  • 函数索引本质是基于虚拟列功能实现的,所以也可以手动创建虚拟列,并在上面建立索引实现相同功能
[8.0.27]>create table t1(a varchar(100) null,b varchar(100) null);
Query OK, 0 rows affected (0.05 sec)


[8.0.27]>alter table t1 add index idx_a(a),add index func_idx_b( (upper(b)) );
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0


[8.0.27]>show index from t1\G
*************************** 1. row ***************************
        Table: t1
   Non_unique: 1
     Key_name: idx_a
 Seq_in_index: 1
  Column_name: a
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: YES
   Expression: NULL
*************************** 2. row ***************************
        Table: t1
   Non_unique: 1
     Key_name: func_idx_b
 Seq_in_index: 1
  Column_name: NULL
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: YES
   Expression: upper(`b`)
2 rows in set (0.02 se

测试函数索引
[8.0.27]>explain select * from t1 where upper(a) = 'ABC'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where
1 row in set, 1 warning (0.01 sec)


[8.0.27]>explain select * from t1 where upper(b) = 'ABC'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: func_idx_b
          key: func_idx_b
      key_len: 403
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec


通过计算列实现函数索引
[8.0.27]>alter table t1 add column c varchar(100) generated always as (upper(a));
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0


[8.0.27]>alter table t1 add index idx_c(c);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0


[8.0.27]>explain select * from t1 where upper(a) = 'ABC'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: idx_c
          key: idx_c
      key_len: 403
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 se

支持json
[8.0.27]>create table userinfo(data json, index( (cast(data ->> '$.name' as char(100))) ));
Query OK, 0 rows affected (0.06 sec)


[8.0.27]>show index from userinfo\G
*************************** 1. row ***************************
        Table: userinfo
   Non_unique: 1
     Key_name: functional_index
 Seq_in_index: 1
  Column_name: NULL
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: YES
   Expression: cast(json_unquote(json_extract(`data`,_utf8mb4\'$.name\')) as char(100) charset utf8mb4)
1 row in set (0.02 sec)


[8.0.27]>explain select * from userinfo where cast(data ->> '$.name' as char(100)) = 'lili'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: userinfo
   partitions: NULL
         type: ref
possible_keys: functional_index
          key: functional_index
      key_len: 403
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.01 se


三、SQL增强

1、CTE公用表表达式

  • 支持递归,可通过cte_max_recursion_depth限制递归次数,默认1000;也可通过max_execution_time限制执行时长(单位毫秒),执行时长默认不限制
  • 其本身类似于临时表、视图
  • 官方文档显示支持对CTE直接进行select、update、delete操作,但实测只支持select,其他的会报语法错误

 

非递归

[8.0.27]>with
    -> ct1(a) as (select 1 num),
    -> ct2(b) as (select a+10 from ct1)
    -> select * from ct1,ct2;
+---+----+
| a | b  |
+---+----+
| 1 | 11 |
+---+----+
1 row in set (0.01 sec)

递归
[8.0.27]>with recursive
    -> ct1(n) as (
    -> select 1
    -> union all
    -> select n+1 from ct1 where n<5
    -> )
    -> select * from ct1;
+------+
| n    |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
+------+
5 rows in set (0.00 sec)

2、窗口函数

2.1、语法

WINDOW window_name AS (window_spec)
    [, window_name AS (window_spec)] ...


window_function_name(window_name/expression)
    OVER (
        [partition_definition]
        [order_definition]
        [frame_definition]
    

FRAME字句:
  • 框架单元(frame_units):
    • ROWS:BETWEEN的范围是行号
    • RANGE:BETWEEN的范围是行的值
  • 框架内容(frame_extent):

CURRENT ROW:边界是当前行,一般和其他范围关键字一起使用

UNBOUNDED PRECEDING:边界是分区中的第一行

UNBOUNDED FOLLOWING:边界是分区中的最后一行

N PRECEDING:ROWS时表示当前行的前N行,RANGE时表示当前行的值减去N

M FOLLOWING:ROWS时表示当前行的后M行,RANGE时表示当前行的值加上M

 

用法:

ORDER BY 字段名 ROWS | RANGE BETWEEN 边界规则1 AND 边界规则2

 

在over子句中未指定frame字句时,默认值是:

有order by子句:RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

无order by子句:RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING

 

动态求和:

[8.0.27]>select *,sum(score) over(
    -> partition by class_id order by score rows between 1 preceding and 1 following
    -> ) as running_range_s
    -> from scorelist
    -> order by class_id,score;
+----------+-------+-----------------+
| class_id | score | running_range_s |
+----------+-------+-----------------+
|        1 |    80 |             166 |
|        1 |    86 |             256 |
|        1 |    90 |             266 |
|        1 |    90 |             280 |
|        1 |   100 |             290 |
|        1 |   100 |             300 |
|        1 |   100 |             200 |
|        2 |    70 |             165 |
|        2 |    95 |             260 |
|        2 |    95 |             190 |
+----------+-------+-----------------+
10 rows in set (0.00 sec)


[8.0.27]>select *,sum(score) over(
    -> partition by class_id order by score range between 5 preceding and 5 following
    -> ) as running_range_s
    -> from scorelist
    -> order by class_id,score;
+----------+-------+-----------------+
| class_id | score | running_range_s |
+----------+-------+-----------------+
|        1 |    80 |              80 |
|        1 |    86 |             266 |
|        1 |    90 |             266 |
|        1 |    90 |             266 |
|        1 |   100 |             300 |
|        1 |   100 |             300 |
|        1 |   100 |             300 |
|        2 |    70 |              70 |
|        2 |    95 |             190 |
|        2 |    95 |             190 |
+----------+-------+-----------------+
10 rows in set (0.00 sec

2.2、普通窗口函数

适用于所有聚合函数

[8.0.27]>select *,sum(profit) over(partition by country) s
    -> from sales order by country,year;
+------+---------+------------+--------+------+
| year | country | product    | profit | s    |
+------+---------+------------+--------+------+
| 2000 | Finland | Computer   |   1500 | 1610 |
| 2000 | Finland | Phone      |    100 | 1610 |
| 2001 | Finland | Phone      |     10 | 1610 |
| 2000 | India   | Calculator |    150 | 1350 |
| 2000 | India   | Computer   |   1200 | 1350 |
| 2000 | USA     | Calculator |     75 | 4575 |
| 2000 | USA     | Computer   |   1500 | 4575 |
| 2001 | USA     | Calculator |     50 | 4575 |
| 2001 | USA     | Computer   |   2700 | 4575 |
| 2001 | USA     | TV         |    250 | 4575 |
+------+---------+------------+--------+------+
10 rows in set (0.01 sec)

2.3、专用窗口函数

  • 序号函数:ROW_NUMBER()、RANK()、DENSE_RANK()
    • ROW_NUMBER():顺序排序,如1、2、3
    • RANK():并列排序,跳过重复序号,如1、1、3
    • DENSE_RANK():并列排序,不跳过重复号,如1、1、2
  • 分布函数:PERCENT_RANK()、CUME_DIST()
    • PERCENT_RANK():每行按照公式(rank-1) / (rows-1)进行计算
    • CUME_DIST():分组内小于、等于当前rank值的行数 / 分组内总行数
  • 前后函数:LAG()、LEAD()
    • LAG():以当前行为准,前第N行的值
    • LEAD():以当前行为准,后第N行的值
  • 头尾函数:FIRST_VALUE()、LAST_VALUE()
    • FIRST_VALUE():返回排行第一的值
    • LAST_VALUE():返回排名最后的值
  • 其它函数:NTH_VALUE()、NTILE()
    • NTH_VALUE():返回当前排序规则第N行的值
    • NTILE():按排名分成N个分区,并记录当前行属于第几分区

 

[8.0.27]>select *,
    -> row_number() over(partition by class_id order by score desc) row_num,
    -> first_value(score) over(partition by class_id order by score desc) fv,
    -> lag(score,1) over(partition by class_id order by score desc) lg,
    -> lead(score,2) over(partition by class_id order by score desc) ld,
    -> ntile(4) over(partition by class_id order by score desc) nt
    -> from scorelist order by class_id,score desc;
+----------+-------+---------+------+------+------+----+
| class_id | score | row_num | fv   | lg   | ld   | nt |
+----------+-------+---------+------+------+------+----+
|        1 |   100 |       1 |  100 | NULL |  100 |  1 |
|        1 |   100 |       2 |  100 |  100 |   90 |  1 |
|        1 |   100 |       3 |  100 |  100 |   90 |  2 |
|        1 |    90 |       4 |  100 |  100 |   80 |  2 |
|        1 |    90 |       5 |  100 |   90 | NULL |  3 |
|        1 |    80 |       6 |  100 |   90 | NULL |  4 |
|        2 |    95 |       1 |   95 | NULL |   70 |  1 |
|        2 |    95 |       2 |   95 |   95 | NULL |  2 |
|        2 |    70 |       3 |   95 |   95 | NULL |  3 |
+----------+-------+---------+------+------+------+----+
9 rows in set (0.01 sec)


简便写法
[8.0.27]>select *,
    -> row_number() over w row_num,
    -> first_value(score) over w fv,
    -> lag(score,1) over w lg,
    -> lead(score,2) over w ld,
    -> ntile(4) over w nt
    -> from scorelist
    -> where class_id < 10
    -> window w as (partition by class_id order by score desc)
    -> order by class_id,score desc;
+----------+-------+---------+------+------+------+----+
| class_id | score | row_num | fv   | lg   | ld   | nt |
+----------+-------+---------+------+------+------+----+
|        1 |   100 |       1 |  100 | NULL |  100 |  1 |
|        1 |   100 |       2 |  100 |  100 |   90 |  1 |
|        1 |   100 |       3 |  100 |  100 |   90 |  2 |
|        1 |    90 |       4 |  100 |  100 |   80 |  2 |
|        1 |    90 |       5 |  100 |   90 | NULL |  3 |
|        1 |    80 |       6 |  100 |   90 | NULL |  4 |
|        2 |    95 |       1 |   95 | NULL |   70 |  1 |
|        2 |    95 |       2 |   95 |   95 | NULL |  2 |
|        2 |    70 |       3 |   95 |   95 | NULL |  3 |
+----------+-------+---------+------+------+------+----+
9 rows in set (0.00 sec)

3、JSON增强

其实从MySQL 5.7的高版本开始,就对JSON数据类型进行了各种加强,8.0 则属于进一步加强,因相关函数实在太多,这里就不做展开讨论,具体见后面的附图。

如果要存储的字符串内容,符合JSON格式标准的(类似于python中的列表和字典),老版本通常会存储到text中,而如果存储到JSON中,则通过相关函数操作(甚至可以通过增加虚拟列建立索引),在读取方面会有天然的速度优势,这是官方在极力推荐的一个功能。

 

下面简单列举例一个8.0的增强点,其他可自行查询官方文档:

--支持切片操作
[8.0.27]>create table test_json_list(id int,data json);
Query OK, 0 rows affected (0.06 sec)


[8.0.27]>insert into test_json_list values(1,'["a","b","c","d"]');
Query OK, 1 row affected (0.01 sec)


[8.0.27]>select *,data->>'$[last-2 to last]' from test_json_list;
+------+----------------------+----------------------------+
| id   | data                 | data->>'$[last-2 to last]' |
+------+----------------------+----------------------------+
|    1 | ["a", "b", "c", "d"] | ["b", "c", "d"]            |
+------+----------------------+----------------------------+
1 row in set (0.00 sec)


--老版本不支持切片
[5.7.37-log]>select *,data->>'$[1 to 3]' from test_json_list;
ERROR 3143 (42000): Invalid JSON path expression. The error is around character position

JSON相关操作函数

 

四、Innodb增强

1、数据字典

  • MySQL 8.0 将数据库元信息都存放于InnoDB存储引擎表中
    • MySQL 8.0将所有原先存放于数据字典文件中的信息,全部存放到数据库系统表中,即将之前版本的frm、opt、trn、trg等文件都移除了,不再通过文件的方式存储数据字典信息。
    • 对INFORMATION_SCHEM,mysql,sys系统库中的存储引擎做了改进,原先使用MyISAM存储引擎的数据字典表都改为使用InnoDB存储引擎存放。
  • 既然所有系统表都使用InnoDB存储引擎,且InnoDB支持事务,那么就延伸出一个新特性:原子DDL操作。
  • INFORMATION_SCHEMA性能提升:在之前版本中,数据字典信息不一定是存放于表中,所以在获取数据字典信息时候,不仅仅是查表操作。例如读取数据库表结构信息,底层其实是读取.frm文件来获得,是一个文件打开读取的操作。而在新版本中,数据字典信息都可以通过直接查表的方式获取。

2、原子DDL操作

MySQL 8.0 开始支持原子 DDL 操作,其中与表相关的原子 DDL 只支持 InnoDB 存储引擎。一个原子 DDL 操作内容包括:更新数据字典,存储引擎层的操作,在 binlog 中记录 DDL 操作。支持与表相关的 DDL:数据库、表空间、表、索引的 CREATE、ALTER、DROP 以及 TRUNCATE TABLE。支持的其他 DDL :存储程序、触发器、视图、UDF 的 CREATE、DROP 以及ALTER 语句。支持账户管理相关的 DDL。

 

8.0执行DDL语句时,DDL⽇志会写入 mysql.innodb_ddl_log表中(innodb_ddl_log字典表隐藏在mysql.ibd 数据字典表空间⾥)。通过设置以下两个参数可以在error.log中看到相关记录。

set global log_error_verbosity=3;

set global innodb_print_ddl_logs=1;

[5.7.37-log]>create table td1(id int);
Query OK, 0 rows affected (0.01 sec)


[5.7.37-log]>show tables like 'td%';
+----------------------+
| Tables_in_test (td%) |
+----------------------+
| td1                  |
+----------------------+
1 row in set (0.00 sec)


[5.7.37-log]>drop table td1,td2;
ERROR 1051 (42S02): Unknown table 'test.td2'


[5.7.37-log]>show tables like 'td%';
Empty set (0.00 s

[8.0.27]>create table td1(id int);
Query OK, 0 rows affected (0.04 sec)


[8.0.27]>show tables like 'td%';
+----------------------+
| Tables_in_test (td%) |
+----------------------+
| td1                  |
+----------------------+
1 row in set (0.01 sec)


[8.0.27]>drop table td1,td2;
ERROR 1051 (42S02): Unknown table 'test.td2'


[8.0.27]>show tables like 'td%';
+----------------------+
| Tables_in_test (td%) |
+----------------------+
| td1                  |
+----------------------+
1 row in set (0.00 sec)


[8.0.27]>drop table if exists td1,td2;
Query OK, 0 rows affected, 1 warning (0.02 sec)


[8.0.27]>show tables like 'td%';
Empty set (0.01


3、自增列持久化

AUTO_INCREMENT自增列,当最大值的行被删除后,数据库进行了重启:

  • MySQL 5.7之前:自增列计数器值只存储在内存中,重启后,计数器值重新设置为:select max(key)+1 from tb; 即自增列key值会出现回溯现象
  • MySQL 8.0:自增列值每次变化时,都将自增计数器的最大值写入 redo log,同时在每次检查点将其写入引擎的私有系统表,解决回溯BUG
[5.7.37-log]>create table t4(
    -> id int not null auto_increment primary key,
    -> number int);
Query OK, 0 rows affected (0.01 sec)


[5.7.37-log]>insert into t4(number) values(10),(20),(30);
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0


[5.7.37-log]>select * from t4;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
|  2 |     20 |
|  3 |     30 |
+----+--------+
3 rows in set (0.00 sec)


[5.7.37-log]>delete from t4 where id>=2;
Query OK, 2 rows affected (0.01 sec)


[5.7.37-log]>select * from t4;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
+----+--------+
1 row in set (0.01 sec)


--重启数据库后
[5.7.37-log]>show create table t4\G
*************************** 1. row ***************************
       Table: t4
Create Table: CREATE TABLE `t4` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)


[5.7.37-log]>insert into t4(number) values(100);
Query OK, 1 row affected (0.00 sec)


[5.7.37-log]>select * from t4;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
|  2 |    100 |
+----+--------+
2 rows in set (0.01 sec)


[5.7.37-log]>update t4 set id=20 where number=100;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0


[5.7.37-log]>show create table t4\G
*************************** 1. row ***************************
       Table: t4
Create Table: CREATE TABLE `t4` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)


[5.7.37-log]>insert into t4(number) values(200);
Query OK, 1 row affected (0.00 sec)


[5.7.37-log]>select * from t4;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
|  3 |    200 |
| 20 |    100 |
+----+--------+
3 rows in set



--删除数据,且重启后
[8.0.27]>show create table t4\G
*************************** 1. row ***************************
       Table: t4
Create Table: CREATE TABLE `t4` (
  `id` int NOT NULL AUTO_INCREMENT,
  `number` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)


[8.0.27]>insert into t4(number) values(100);
Query OK, 1 row affected (0.01 sec)


[8.0.27]>select * from t4;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
|  4 |    100 |
+----+--------+
2 rows in set (0.01 sec)


[8.0.27]>update t4 set id=20 where number=100;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0


[8.0.27]>show create table t4\G
*************************** 1. row ***************************
       Table: t4
Create Table: CREATE TABLE `t4` (
  `id` int NOT NULL AUTO_INCREMENT,
  `number` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)


[8.0.27]>insert into t4(number) values(200);
Query OK, 1 row affected (0.01 sec)


[8.0.27]>select * from t4;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
| 20 |    100 |
| 21 |    200 |
+----+--------+
3 rows in set (0.0


4、锁定语句选项

新增选项语句:nowait、skip locked

  • nowait:获取不到锁就直接报错,执行失败
  • skip locked:跳过被锁定的行
--session 1
[8.0.27]>select * from t4;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
| 20 |    100 |
| 21 |    200 |
+----+--------+
3 rows in set (0.00 sec)


[8.0.27]>start transaction;
Query OK, 0 rows affected (0.00 sec)


[8.0.27]>update t4 set number = 101 where id = 20;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0


--session 2
[8.0.27]>select * from t4 for update nowait;
ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.


[8.0.27]>select * from t4 for share skip locked;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
| 21 |    200 |
+----+--------+
2 rows in set (0.00 sec)


[8.0.27]>select * from t4 for update skip locked;
+----+--------+
| id | number |
+----+--------+
|  1 |     10 |
| 21 |    200 |
+----+--------+
2 rows in set (0.00

五、实例克隆

实例克隆功能会在目标服务器克隆一份与源服务器一样的物理文件。

  • 克隆时,如果指定了data directory参数,会将数据文件克隆到该目录下,但是不会自动启动,如果想按指定的data directory启动数据库,需要自己修改my.cnf中对应的参数后进行启动
  • 克隆时,如果未指定data directory参数,则会删除当前实例datadir下的文件,全部克隆完毕后,自动重启数据库实例(慎重,如果登陆错服务器,执行了该命令,就真的是删库跑路了)。

 

实例克隆可以本地克隆,也可以远程克隆,两者原理一样。另外,克隆功能对主从实例有着极其苛刻的要求:

  • 操作系统版本必须一致
  • mysql主版本号、发行级别、发行系列版本号都必须一致,mysql8.0.19->mysql8.0.20 这种不支持
  • 目前仅支持InnoDB引擎,其他引擎不支持,如果有非InnoDB引擎信息会在日志里面显示
  • 支持压缩表clone操作,但文件系统的block size需要保持一致
  • 在8.0.27之前不支持DDL操作(因为有backup lock),在8.0.27版本及以后,允许DDL操作,但不允许table space的操作(比如import、export)

 

优势:

  • 相比较于Xtrabackup而言,备份速度很快,效率几乎可以翻倍。
  • 可以远程备份。虽然Xtrabackup也可以实现流式物理备份到远程服务器,但需要解决SSH的问题,这在生产环境中,大部分情况下可能是不允许的;相比较而已,克隆功能在这方面则完全不需要考虑。

 

Xtrabackup物理备份过程:

  • 拷贝重做日志文件(持续整个备份周期)
  • 拷贝InnoDB 文件
  • 保存binlog位点(加全局锁)
  • 拷贝其他非InnoDB 文件

 

Clone物理备份过程:

  • 拷贝InnoDB 文件(不支持非Innodb引擎)
  • 对buffer pool中的脏页按(space,page_no)顺序进行备份,同时启用 page tracking 的机制,记录当前已经 check point 完成的 LSN,减少需要拷贝的重做日志。
  • 拷贝check point之后的重做日志即可

 

从上述备份过程的对比中,不难看出,还原完物理文件后,恢复的数据库位点就不一样,那同样的,后续需要恢复binlog的量肯定也不一样。可以猜想,如果源实例足够大,备份和还原的时间足够长,后续日志重放以及追数所需时间等,总结起来就是,clone都能完胜xtrabackup。

 

操作过程:

--master,创建clone插件,以及源端克隆用户,用户需要backup_admin权限
[8.0.27]>install plugin clone soname 'mysql_clone.so';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>create user clone_src@'%' identified by 'abc123';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>grant backup_admin on *.* to clone_src@'%';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>create user replicater@'%' identified by 'abc123';
Query OK, 0 rows affected (0.01 sec)


[8.0.27]>grant replication slave,replication client on *.* to replicater@'%';
Query OK, 0 rows affected (0.00 sec)


[8.0.27]>flush privileges;
Query OK, 0 rows affected (0.00


--slave,创建clone插件,目标端数据库用户需要有clone_admin权限,比如root
[8.0.27]>install plugin clone soname 'mysql_clone.so';
Query OK, 0 rows affected (0.02 sec)


--设置源服务器信息,开始克隆
[8.0.27]>set global clone_valid_donor_list = '172.31.255.141:3380';
Query OK, 0 rows affected (0.00 sec)


--IP是指源服务器的IP,而非mysql用户clone_src的IP
[8.0.27]>clone instance from 'clone_src'@'172.31.255.141':3380  identified by 'abc123';
Query OK, 0 rows affected (1 min 47.63 sec)


--搭建复制
[8.0.27]>change master to
    -> master_host='172.31.255.141',
    -> master_port=3380,
    -> master_user='replicater',
    -> master_password='abc123',
    -> master_auto_position=1;
Query OK, 0 rows affected, 8 warnings (0.04 sec)


[8.0.27]>start slave;
Query OK, 0 rows affected, 1 warning (0.03 

六、MySQL复制改进

1、二进制日志压缩

日志压缩通过以下两个参数控制,只支持ROW模式:

binlog_transaction_compression:OFF/ON,默认值OFF,很容易理解,就是是否开启日志压缩

binlog_transaction_compression_level_zstd:1-22,默认值3,调高压缩级别未看到有明显区别,详情未知。

 

压缩效果:测试效果很明显,压缩率40%出头,同样的操作,压缩前205M,压缩后85M。

在主从复制中,开启压缩,能缓解因网络带宽所带来的主从延迟现象;另外,从库接受到日志后,依然是以压缩状态存放到中继日志里,SQL线程会在回放日志是进行解码和解压操作。

 

2、日志中保存元数据表信息

通过设置参数binlog_row_metadata=FULL,将表的主键和列信息记录到binlog中,参数为关闭状态(参数值为:MINIMAL);显而易见的是,开启这个特性,保存元数据信息会增加一定额外的开销。

 

七、其他改进功能

1、全局变量持久化

  • MySQL 5.7及以前的版本,修改参数只有session和global两种,重启MySQL后都会以配置文件重新初始化。
  • MySQL 8.0可通过persist及persist_only来进行参数持久化,参数信息保存到datadir的mysqld-auto.cnf文件中,他们的设置范围都是global级别。
    • persist:修改当前内存值,同时持久化,如 set persist long_query_time=3;
    • persist_only:不修改当前内存值,只对参数进行持久化,如 set persist_only long_query_time=3;
  • 参数持久化需要用有 system_variables_admin 和 persist_ro_variables_admin 权限。
  • 持久化文件mysqld-auto.cnf是以json格式保存参数信息,也可以通过查询performance_schema.persisted_variables来获取持久化信息。
  • 数据库启动时,会先读取my.cnf配置文件,再读取mysqld-auto.cnf文件,这意味着,同一个参数,持久化文件的值会将my.cnf的覆盖掉。
  • 执行reset persist命令,或者删除mysqld-auto.cnf文件(需要重启)都可以清空持久化信息。

 

mysqld-auto.cnf 文件:

{
    "Version": 1,
    "mysql_server": {
        "long_query_time": {
            "Value": "3.000000",
            "Metadata": {
                "Timestamp": 1650809692554275,
                "User": "root",
                "Host": "localhost"
            }
        }
    }
}

[8.0.27]>select * from performance_schema.persisted_variables;
+-----------------+----------------+
| VARIABLE_NAME   | VARIABLE_VALUE |
+-----------------+----------------+
| long_query_time | 3.000000       |
+-----------------+----------------+
1 row in set (0.00 sec)

2、秒级加列

从MySQL 8.0.12版本开始引入快速加列功能,采用的算法为instant:

  • 在 ALTER 语句后增加 ALGORITHM=INSTANT 即代表使用 INSTANT 算法, 如果未明确指定,则支持 INSTANT 算法的操作会默认使用。如果指定 ALGORITHM=INSTANT 但不支持,则操作立即失败并显示错误。
  • 使用INSTANT算法后,添加列时不再rebuild整个表,只在表的 metadata 中记录新增列的基本信息即可。

 

从后面的表格中可以看到,INSTANT算法的应用场景有限,用的最多的应该算是添加列了,同时,他还有以下其他的限制条件:

  • 添加列的ALTER语句,如果还包含其他不支持INSTANT算法的操作,那么ALTER语句会报错,且整个语句所有操作都不会执行。
  • 只能顺序加列, 仅支持在最后添加列,而不支持在现有列的中间添加列。
  • 不支持压缩表,即该表行格式不能是 COMPRESSED。
  • 不支持包含全文索引的表。
  • 不支持临时表。
  • 不支持那些在数据字典表空间中创建的表。

 

以下为INSTANT和IN PLACE算法的对比,*表示不全部支持:

 

[8.0.27]>select TABLE_SCHEMA,TABLE_NAME,TABLE_ROWS,
    -> (DATA_LENGTH+DATA_FREE+INDEX_LENGTH)/1024/1024 tb_size
    -> from information_schema.tables where table_name='salaries_test';
+--------------+---------------+------------+---------------+
| TABLE_SCHEMA | TABLE_NAME    | TABLE_ROWS | tb_size       |
+--------------+---------------+------------+---------------+
| emp          | salaries_test |   30500061 | 1136.00000000 |
+--------------+---------------+------------+---------------+
1 row in set (0.01 sec)


[8.0.27]>alter table salaries_test add column num int,algorithm=inplace;
Query OK, 0 rows affected (56.26 sec)
Records: 0  Duplicates: 0  Warnings: 0


[8.0.27]>alter table salaries_test add column a varchar(100),algorithm=instant;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0


[8.0.27]>alter table salaries_test add column b varchar(100), 
    -> add column c varchar(100) after num,algorithm=instant;
ERROR 1845 (0A000): ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=COPY/INPLACE.


[8.0.27]>alter table salaries_test add column b varchar(100);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warning


3、默认字符集

utf8mb4

 

八、升级MySQL 8.0

MySQL本身支持跨版本升级到8.0,升级过程非常简单,获取一个已编译好的安装包,解压后,用8.0的server路径替换老版本的server路径,然后直接启动数据库就可以。启动过程中,MySQL 8.0会自动升级系统表,而不需要用户手动执行mysql_upgrade,启动完毕后即可访问8.0数据库。

 








posted @ 2022-09-24 21:20  百老汇大管家  阅读(260)  评论(0)    收藏  举报