mysql笔记

查看mysql 用户的连接权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select host , user from user;
+-----------+---------------+
| host | user |
+-----------+---------------+
| % | root |
| localhost | mysql.session |
| localhost | mysql.sys |
| localhost | root |
+-----------+---------------+
4 rows in set (0.00 sec)

可见允许root用户在任何IP段下连接数据库实例

mysql连接方式

1
2
3
4
5
6
1. TCP/IP方式:本地或远程登录
mysql -hhost -uname -ppassword
2. socket方式:本地登录
[root@localhost ~]# ps -ef | grep mysql
root 3076 1 0 Oct14 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe --datadir=/var/lib/mysql --socket=/var/lib/mysql/mysql.sock --pid-file=/var/run/mysqld/mysqld.pid --basedir=/usr --user=mysql
mysql -uname -S/var/lib/mysql/mysql.sock

InnoDB 存储结构:B+树

MyIsam引擎是默认存储引擎,对应磁盘上三个文件:.frm(存储表定义) .MYD(存储数据) .MYI(存储索引)

InnoDB引擎,对应磁盘上两个文件:.frm(存储表定义).ibd(存储聚集索引,叶子节点即数据)

1
2
3
4
5
6
7
8
9
InnoDB的一张表的数据和索引都存在表空间,一个表空间对应一个ibd文件

表空间由段、区、页组成
段由多个区组成
区由连续的多个页组成(页大小默认为16KB)
页存放table的行数据

B+树叶子节点:存储 数据段
B+树非叶子节点:存储 索引段

索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 聚集索引:
一张表只有一个聚集索引,因为InnoDB的数据存储就是按照主键顺序存放的一颗B+树,此时B+树的叶子节点存放的是整张表的行记录数据。
聚集索引,适合基于主键的排序查找和范围查找

2. 辅助索引:
辅助索引的叶子节点不存储数据,而是存储聚集索引的主键值(因此主键不宜是占太大的空间的字段)。
当通过辅助索引查找数据时,需要先在辅助索引B+树找到对应的聚集索引的主键值,再到聚集索引B+树查找指定主键值所对应的数据

3. 建立索引的原则:
选择取值重复度低(区分度高)的字段建立索引,比如用户名适合建立索引,性别就不适合建立索引
不要重复创建索引
尽量扩展已有索引,而不是添加新的多余的索引
mysql优化器会对使用索引对语句进行判断,当使用辅助索引的语句扫描行数超过30%(大概值)时,mysql优化器会选择走聚集索引的全表扫描

4. 只select 需要的字段(可能会用到覆盖索引,只需要从辅助索引检索即可查到所需数据,更快),不要select *

5. 联合索引依照最左匹配的顺序来检索B+树,直到遇到范围查询(> < between like)停止匹配,所以经常进行范围查询的字段在联合索引中要放在最后。比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整

6. 使用explain/desc 查看执行计划,找出扫描行很多(rows)的语句,修改sql语句使得扫描行尽量少即可

查看索引(Cardinality:基数值越大,区分度越高,适合建立索引)

1
show index from table_name;

添加索引

1
2
3
4
5
6
7
8
// 普通索引
alter table table_name add index index_name(column_list);
create index index_name on table_name(column_list);
// 唯一索引
alter table table_name add unique(column_list);
create unique index index_name on table_name(column_list);
// 主键索引
alter table table_name add primary key(column_list);

explain/desc 查看执行计划:

±—±-----------------±--------±-------------±--------±-------------------±------±-----------±-----±-------±---------±-----------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

±—±-----------------±--------±--------------±-------±--------------------±------±-----------±-----±-------±---------±----------+

id: 查询序号

id相同:执行顺序由上至下

id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

id相同又不同(两种情况同时存在):id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

select_type:查询类型

SIMPLE:简单的select查询,查询中不包含子查询或者union

PRIMARY:查询中包含任何复杂的子部分,最外层查询则被标记为primary

SUBQUERY:在select 或 where列表中包含了子查询

DERIVED:在from列表中包含的子查询被标记为derived(衍生),mysql或递归执行这些子查询,把结果放在临时表里

table:查询的数据表,当从衍生表中查数据时会显示<derivedx>x 表示对应的执行计划id

partitions:表分区,表创建的时候可以指定通过那个列进行表分区

type:访问类型,性能由好到坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

system:表只有一行记录

const:表示通过索引一次就找到了,且只有一条匹配记录;const用于比较primary key 或者 unique索引

eq_ref:唯一性索引或主键扫描

ref:非唯一性索引扫描,返回匹配某个单独值的所有行

range:只检索给定范围的行,使用一个索引来选择行

index:Full Index Scan,遍历索引

ALL:Full Table Scan,遍历全表

possible_keys:查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用

key:实际使用的索引,如果为NULL,则没有使用索引

key_len:查询中使用到的索引的最大可能长度

ref:显示使用到的索引字段或常量查询(const)

rows:估算找到所需的记录所需要读取的行数

extra:额外信息

Using filesort :使用非索引列进行排序时出现,非常耗性能

Using temporary: 使用临时表保存中间结果,常见于order by 和 group by 、join子查询

Using index:使用了覆盖索引,避免了访问聚集索引数据行,效率高

Using where:使用了where子句来过滤结果集

Limit查询

mysql通过limit实现分页查询,语法如下:

1
2
3
4
5
// 返回前10条记录
select * from user limit 10;
// 返回从offset=50000开始的前10条记录
// 性能低的limit offset count查询
select * from user limit 50000, 10;

当offset过大时,分页查询性能急剧下降(抛开mysql的查询缓存),为了提升limit查询的性能,优化方向有以下种:

  1. 使用覆盖索引子查询,查询所需offset的最小主键值,然后通过主键范围查询的where子句来得到结果,例如:

    1
    2
    // 覆盖索引子查询 + where 范围查询 + limit count查询
    select * from user where user_id >= (select user_id from user order by user_id limit 50000, 1) limit 10;
  2. 使用覆盖索引子查询,查询所需offset的最小主键值,然后通过JOIN联表查询的where子句来得到结果,例如:

    1
    2
    // 覆盖索引子查询 + JOIN + where + limit count 查询
    select * from user as t1 inner join (select user_id from user order by user_id limit 50000, 1) as t2 where t1.user_id >= t2.user_id limit 10;
    1
    2
    3
    select t1.* from user_task t1 where t1.task_no in (100, 110, 120, 130, 140) limit 46000, 10; --- 百万数据查询耗时2~3秒
    select t1.* from user_task t1 inner join (select id from user_task where task_no in (100, 110, 120, 130, 140) order by id asc limit 46000, 10) t2 on t1.id=t2.id; --- 百万数据查询耗时0.1秒左右
    ---性能提升20~30倍
  3. 业务优化,根据业务逻辑确定其他字段的值来代替limit的offset值

优化

  1. 查看sql具体执行时间,具体查看官方文档
1
2
3
4
5
6
7
mysql> set profiling=1;
mysql> select * from test;
mysql> show profiles;
mysql> show profile;
mysql> show profile for query 1;
mysql> show profile all for query 2;
mysql> show status like "last_query_cost";
  1. mysql 性能监控performance_schema,具体查看官方文档
1
2
mysql> show databases;
mysql> use performance_schema;

事务查询

1
2
3
4
5
mysql> show full processlist;
mysql> select * from information_schema.innodb_trx\G
mysql> select * from information_schema.innodb_locks\G
mysql> select * from information_schema.innodb_lock_waits\G
mysql> select * from sys.innodb_lock_waits\G

InnoDB在Repeatable Read事务隔离级别下,使用快照读,快照读不会占用和等待表锁

应该显示的使用事务,不要使用autocommit

数据库更新丢失问题

并发事务中进行查询并更新可能会导致更新丢失。说白了就是查询并更新两条sql不是一个原子操作,而且更新语句中使用了查询出来的旧值,或者更新语句是根据查询语句的旧值进行业务逻辑的更新,此时其他事务的更新才会被覆盖。如果更新语句和查询语句的结果不要建立关系,则没问题,比如 update bank set money = money - 100 where id = 123;

例如:mysql 事务级别Repeatable Read下:

SET AUTOCOMMIT=0;

CREATE TABLE bank (
id int(10) NOT NULL AUTO_INCREMENT,
money int(10) NOT NULL,
PRIMARY KEY (id)
);

INSERT INTO bank VALUES (1, 900);

事务1(存100元) 事务2(取100元)
begin;
begin;
select * from bank where id=1;(900)
select * from bank where id=1; (900)
update bank set money=1000 where id=1;
update bank set money=800 where id=1;(阻塞直到事务1commit)
commit;
commit; (事务1的更新被覆盖)

解决办法1:悲观锁(select for update)

事务1(存100元) 事务2(取100元)
begin;
begin;
select * from bank where id=1 for update;
select * from bank where id=1 for update;(阻塞直到事务1commit)(1000)
update bank set money=1000 where id=1;
update bank set money=900 where id=1;(基于1000减去100)
commit;
commit;

解决办法2:乐观锁-旧值判断

事务1(存100元) 事务2(取100元)
begin;
begin;
select * from bank where id=1;
select * from bank where id=1;
update bank set money=1000 where id=1 and money=900;
update bank set money=800 where id=1 and money=900;(阻塞直到事务1commit,但是此条更新会失败)
commit;
commit;

解决办法3:乐观锁-表上增加版本字段

CREATE TABLE bank (
id int(10) NOT NULL AUTO_INCREMENT,
money int(10) NOT NULL,
timeStamp int(10) DEFAULT NULL,
PRIMARY KEY (id)
);

INSERT INTO bank VALUES (1, 900, 1571129028);

事务1(存100元) 事务2(取100元)
begin;
begin;
select * from bank where id=1;
select * from bank where id=1;
update bank set money=1000, timeStamp=1571129243 where id=1 and timeStamp=1571129028;
update bank set money=800, timeStamp=1571129246 where id=1 and timeStamp=1571129028;(阻塞直到事务1commit,但是此条更新会失败)
commit;
commit;

一致读vs当前读

参考:事务到底是隔离的还是不隔离的

可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

而读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:

  • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
  • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

清空表

1
2
3
4
truncate vs delete vs drop
truncate 是ddl操作,仅删除数据,不支持条件过滤,不能回滚,较快
delete 是dql操作,仅删除数据,支持条件过滤,可以回滚,慢
drop 是ddl操作,删除数据同时删除表结构,最快

分库分表

分库database:分库的目的是降低单台服务器负载,切分原则是根据业务紧密程度拆分,缺点是跨数据库无法联表查询

分表table:当单表数据量超大的时候,B-Tree索引就无法起作用了。除非是覆盖索引查询,否则数据库服务器需要根据索引扫描的结果回表(回主键索引查询叶子节点行记录),查询所有符合条件的记录,如果数据量巨大,这将产生大量随机I/O,随之,数据库的响应时间将大到不可接受的程度。另外,索引维护(磁盘空间、I/O操作)的代价也非常高

分表分为:垂直分表和水平分表

垂直分表:就是表的字段太多,将字段拆分到不同的表中。

水平分表:每个表的字段一样,存储的记录不一样,可以根据业务逻辑进行拆分(user_id,地区,时间,hash等)

水平分表后的问题:

​ 怎么设置全局唯一主键(现成方案:snowflake等)

​ 怎么保证数据均匀存储,没有热点问题(根据业务逻辑来)

​ 怎么方便扩容和缩容(一致性hash)

分区

分表存在的问题是需要服务端修改,引入中间件来屏蔽分库分表带来的差异

分区相当于mysql层的分表,对服务端是透明的,服务端不需要做任何修改

而且分区的扩容和缩容很容易,直接增加分区,删除分区即可,对数据完整性没影响

所以不存在单机负载问题的情况下,可以用分区来代替分表,简化服务端的更改

参考MySQL分区与传统的分库分表

mysql repication

主从复制备份,读写分离

mysql fabric

在replication的基础上,提供故障检测和自动Failover、请求路由和分片(sharding)功能

mysql cluster

面向大规模数据的存储解决方案,Cluster的架构思想与Hadoop非常类似,它设计的前提是“认为每个Node都是易于出错的”、集群规模巨大、多租户,所以它提供了数据备份机制、自动迁移、自动Failover等特性,来保证可用性、健壮性。它的核心特性为:数据集并不是存储某个特定的MySQL实例上,而是被分布在多个Data Nodes中,即一个table的数据可能被分散在多个物理节点上,任何数据都会在多个Data Nodes上冗余备份。任何一个数据变更操作,都将在一组Data Nodes上同步以保证数据的一致性。

DUAL表

Mysql里面很多内置函数的用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> select rand();
+-------------------+
| rand() |
+-------------------+
| 0.783871779017031 |
+-------------------+
1 row in set (0.00 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 0 |
+------------------+
1 row in set (0.00 sec)

mysql> select sysdate();
+---------------------+
| sysdate() |
+---------------------+
| 2020-05-19 01:54:25 |
+---------------------+
1 row in set (0.00 sec)

其实他们都等同于如下语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> select rand() from dual;
+--------------------+
| rand() |
+--------------------+
| 0.7727739147588367 |
+--------------------+
1 row in set (0.00 sec)

mysql> select last_insert_id() from dual;
+------------------+
| last_insert_id() |
+------------------+
| 0 |
+------------------+
1 row in set (0.00 sec)

mysql> select sysdate() from dual;
+---------------------+
| sysdate() |
+---------------------+
| 2020-05-19 01:56:36 |
+---------------------+
1 row in set (0.01 sec)

DUAL表是一张虚拟表,纯粹是为了满足 select from where这一语法习惯而设置的。

实际应用中可以用于,条件查询不存在则插入的操作:

比如新建一个角色表

1
2
3
4
5
6
7
8
9
10
11
12
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`no` int(11) NOT NULL,
`type` int(11) DEFAULT NULL,
`remark` varchar(255) DEFAULT NULL,
PRIMARY KEY (`seqid`,`no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

条件插入语句可以如下:

先select * from role where name=‘xxoo’;判断是否存在,不存在,再insert into role (id, name, no, type, remark ) values (…);

使用DUAL表一条语句搞定:

insert into role (id, name, no, type, remark ) select 123, ‘test-dual’, 456, 1, ‘test-dual’ from dual where not exists (select * from role where name=‘test-dual’);

日志

Mysql日志类型:

  1. redo log: 在事务开始的时候先写redo log,以防发生故障时,在恢复的时候根据redo log进行重做日志,保证数据的一致性

  2. undo log:在事务开始前将当前数据库版本生成undo log,用于事务回滚操作

  3. bin log:用于主从复制,从库连接到主库,读取bin log进行主从同步。在事务提交的时候会刷新bin log

  4. relay log:用于主从复制,从库将主库读到的binlog复制到从库的relay log中,然后回放relay log以达到主从同步

  5. error log:

    1
    2
    [mysqld]
    log-error = /var/log/mysql/error.log
  6. query log

    1
    2
    3
    [mysqld]
    general_log_file=/var/log/mysql/query.log
    general_log=ON
  7. slow query log

    1
    2
    3
    4
    5
    6
    7
    8
    #1. 查看mysql配置文件
    mysql --help | grep my.cnf
    /etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf

    #2. 修改配置文件:vim /etc/my.cnf
    slow_query_log=ON
    slow_query_log_file=/var/log/slowmysql.log
    long_query_time=10

查看某个操作的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> ? create table
Name: 'CREATE TABLE'
Description:
Syntax:
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
(create_definition,...)
[table_options]
[partition_options]

mysql> help prepare
Name: 'PREPARE'
Description:
Syntax:
PREPARE stmt_name FROM preparable_stmt

字符集和校对规则

字符集指定字符的编码方式。比如Mysql推荐使用的字符集为:utf8mb4

校对规则制定字符如何排序及比较大小。比如Mysql推荐使用的校对规则为:utf8mb4_unicode_ci (ci结尾表示case insensitive)

Mysql对字符集的转换处理流程如下:

  1. 客户端请求数据库数据,发送的数据使用character_set_client字符集
  2. MySQL实例收到客户端发送的数据后,将其转换为character_set_connection字符集
  3. 进行内部操作时,将数据字符集转换为内部操作字符集:
  4. 使用每个数据字段的character set设定值
  5. 若不存在,使用对应数据表的default character set设定值
  6. 若不存在,使用对应数据库的default character set设定值
  7. 若不存在,使用character_set_server设定值
  8. 将操作结果值从内部操作字符集转换为character_set_results

以防中文显示乱码,将character_set_client、character_set_connection、character_set_results、character_set_server都设置为utf8mb4;

查看默认字符集和校对规则

1
2
mysql> SHOW VARIABLES LIKE "%char%";
mysql> SHOW VARIABLES LIKE "%coll%";

设置字符集和校对规则

  1. 启动mysql时设置:修改配置文件
1
2
3
4
5
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
[client]
default-character-set=utf8mb4
  1. 创建数据库时指定
1
2
3
CREATE DATABASE myDb
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
  1. 创建表时指定
1
CREATE TABLE myTb () ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

mysql默认字符集为latin,如果不存储中文字符,使用默认即可,节省内存,如果要存储中文,可以对存储中文的特定字段设置字符集

SQL注入

在输入SQL参数的地方输入了特殊的SQL语句。防止SQL注入:对参数进行检查转义。

Mybatis的参数引用方式:#{param}${param},其中#{}形式的SQL语句会创建一个预编译语句PreparedStatement,参数会用?占位符来标识,PreparedStatement里会对输入参数进行转义处理。而${}形式的SQL语句会直接用输入参数进行字符串替换。

另外PreparedStatement的预编译功能是依赖Mysql数据库服务器的。Mysql的预编译功能如下:

1
2
3
4
5
6
7
8
mysql> prepare selectVodById from 'select * from vods where vodid = ?';
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> set @vodId='34 or 1 = 1';
Query OK, 0 rows affected (0.00 sec)

mysql> execute selectVodById using @vodId\G

Mysql JDBC Connector驱动从5.0开始默认关闭了服务器预编译功能,就是说PreapredStatement的执行效果和Statement在服务器上是一样的。只有显式开启了服务器预编译功能,PreparedStatement才会被预编译。直接在数据库url中添加useServerPrepStmts=true即可:

1
jdbc:mysql://localhost:3306/test?useServerPrepStmts=true

Druid连接池+监控

一: 配置数据库密码加密

  1. 执行命令,生成密码加密后的的privateKey、publicKey和加密后的password
1
java -cp druid-1.1.17.jar com.alibaba.druid.filter.config.ConfigTools 12345678
1
2
3
privateKey:MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAjdRL/KlKXM5TjMHSRZC/Gq45a/DvEBXIRBDUHodCmQrs4HkB4kMsEuYJ5i6ID45XUZw8ibyyCNrcx9FNkj6NSwIDAQABAkEAgbMO+jNmSZB1X1cwD2XbHW8OG+Ps+uywg25QTMqs4H55W+D9rThzDpgWBHbj1o8hupKOm1aoeeZf0itIi8TggQIhANrioKxjzAHRpNTTr5KsJWMRjIzhmn3KZCwpyNkZItoxAiEApeDSq/268bRIEsLapBHYfZA6Sy7M4JJCKPxv6pfFhDsCIQCLZk3BvIUOm3+Ic5CbrrrYzzJd/sgvWJhXb/0UFmgV4QIgHCh2+rU+p8sXtP+Yx+MzodT64EpYgwKw8m4vvV34LIMCIGh979qmx/BZk5ZpsOf4Y3H8V9G8ApTIHUCORs5gbIDn
publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAI3US/ypSlzOU4zB0kWQvxquOWvw7xAVyEQQ1B6HQpkK7OB5AeJDLBLmCeYuiA+OV1GcPIm8sgja3MfRTZI+jUsCAwEAAQ==
password:WusbaYnEH4csLTFPt/XFOoQ6oLsPFDV//UVQCW0+gk+r0jx0qvBvZRBH9OVbjV9Wuw8ELRUsWtnQYHXuamkz1g==
  1. 将publicKey和加密后的password写入配置文件,如下
1
2
3
4
5
6
7
8
9
10
11
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=yes&characterEncoding=UTF-8&useSSL=false
username: root
password: WusbaYnEH4csLTFPt/XFOoQ6oLsPFDV//UVQCW0+gk+r0jx0qvBvZRBH9OVbjV9Wuw8ELRUsWtnQYHXuamkz1g==
druid:
filter:
config:
enabled: true
connection-properties: config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAI3US/ypSlzOU4zB0kWQvxquOWvw7xAVyEQQ1B6HQpkK7OB5AeJDLBLmCeYuiA+OV1GcPIm8sgja3MfRTZI+jUsCAwEAAQ==

二:配置监控

  1. 配置文件添加具体的filter,如下
1
2
3
4
5
6
7
8
spring:
datasource:
druid:
stat-view-servlet:
enabled: true
web-stat-filter:
enabled: true
filters: stat,wall,slf4j
  1. 启动应用,访问/druid/index.html

GroupBy+Having使用场景

比如员工表employee如下:

“id” “name” “dept” “salary” “hire_date”
“1” “张三” “开发部” “1200000” "1/10/2019 17:34:05"
“2” “李四” “开发部” “1500000” "21/4/2020 17:34:48"
“3” “王五” “设计部” “900000” "4/8/2020 17:35:23"
“4” “刘坡” “测试部” “1500000” "20/10/2020 17:36:16"
“5” “钱八” “销售部” “800000” "1/4/2019 17:36:48"
“6” “孙久” “销售部” “950000” "28/4/2020 17:37:29"
“7” “麻子” “设计部” “1100000” "15/10/2020 17:38:10"
“8” “曹弯” “开发部” “2200000” “20/7/2020 17:39:03”

查询自2020年以来入职的员工中哪个部门的最低工资是最低的:

1
select dept, min(salary) as minimum from employee where hire_date > '2020-01-01' group by dept having minimum>1000000 order by minimum limit 1;
-------------本文结束感谢您的阅读-------------
Good for you!