当前位置: 首页 > news >正文

记录些MySQL题集(16)

MySQL 存储过程与触发器

一、初识MySQL的存储过程

Stored Procedure存储过程是数据库系统中一个十分重要的功能,使用存储过程可以大幅度缩短大SQL的响应时间,同时也可以提高数据库编程的灵活性。

存储过程是一组为了完成特定功能的SQL语句集合,使用存储过程的目的在于:将常用且复杂的SQL语句预先写好,然后用一个指定名称存储起来,这个过程经过MySQL编译解析、执行优化后存储在数据库中,因此称为存储过程。当以后需要使用这个过程时,只需调用根据名称调用即可。

其实存储过程和Java中的方法、其他语言中的函数十分类似,也就是先将一堆代码抽象成一个函数,当之后需要使用时,不需要再重写一遍代码,而是直接根据名称调用相应的函数/方法即可。

对比常规的SQL语句来说,普通SQL在执行时需要先经过编译、分析、优化等过程,最后再执行,而存储过程则不需要,一般存储过程都是预先已经编译过的,这就好比JIT即时编译器一样,为了提升一些常用代码的执行效率,JIT会将热点代码编译成本地机器码,以此省略解释器翻译执行的步骤,从而做到提升性能的目的。

但使用存储过程有利有弊,具备的优点如下:

复用性:存储过程被创建后,可以在程序中被反复调用,不必重新编写该存储过程的SQL语句,同时库表结构发生更改时,只需要修改数据库中的存储过程,无需修改业务代码,也就意味着不会影响到调用它的应用程序源代码。

灵活性:普通的SQL语句很难像写代码那么自由,而存储过程可以用流程控制语句编写,也支持在其中定义变量,有很强的灵活性,可以完成复杂的条件查询和较繁琐的运算。

省资源:普通的SQL一般都会存储在客户端,如Java中的dao/mapper层,每次执行SQL需要通过网络将SQL语句发送给数据库执行,而存储过程是保存在MySQL中的,因此当客户端调用存储过程时,只需要通过网络传送存储过程的调用语句和参数,无需将一条大SQL通过网络传输,从而可降低网络负载。

高性能:存储过程执行多次后,会将SQL语句编译成机器码驻留在线程缓冲区,在以后的调用中,只需要从缓冲区中执行机器码即可,无需再次编译执行,从而提高了系统的效率和性能。

安全性:对于不同的存储过程,可根据权限设置执行的用户,因此对于一些特殊的SQL,例如清空表这类操作,可以设定root、admin用户才可执行。同时由于存储过程编写好之后,对于客户端而言是黑盒的,因此减小了SQL被暴露的风险。

但还是那句话,凡事有利必有弊,存储过程也会带来一些之前不存在的问题:

CPU开销大:如果一个存储过程中涉及大量逻辑运算工作,会导致MySQL所在的服务器CPU飙升,因而会影响正常业务的执行,有可能导致MySQL在线上出现抖动,毕竟MySQL在设计时更注重的是数据存储和检索,对于计算性的任务并不擅长。

内存占用高:为了尽可能的提升执行效率,因此当一个数据库连接反复调用某个存储过程后,MySQL会直接将该存储过程的机器码放入到连接的线程私有区中,当MySQL中的大量连接都在频繁调用存储过程时,这必然会导致内存占用率同样飙升。

维护性差:一方面是过于复杂的存储过程,普通的后端开发人员很难看懂,毕竟存储过程类似于一门新的语言,不同语言之间跨度较大。另一方面是很少有数据库的存储过程支持Debug调试,MySQL的存储过程就不支持,这也就意味着Bug出现时,无法像应用程序那样正常调试排查,必须得采取“人肉排查”模式,即一步步拆解存储过程并排查。

基于上述原因,咱们在不必要使用存储过程的情况下,就尽量减少存储过程的编写,除非特定的业务需求导致不得不用时,再将注意力转向这块。

二、存储过程的定义、调用与管理

2.1、存储过程的语法

先来看看存储过程的定义语法,如下:

DELIMITER $-- 创建的语法:指定名称、入参、出参
CREATE
PROCEDURE存储过程名称(返回类型参数名1参数类型1,....)
[...这里在后面讲...]
-- 表示开始编写存储过程体
BEGIN
-- 具体组成存储过程的SQL语句....
-- 表示到这里为止,存储过程结束
END $DELIMITER ;

存储过程的BEGIN、END就类似于Java方法的{},用来区分起始和结束的边界。所有语言的函数/方法定义时,一般都会分为四类,如下:

  • • ①无参无返回。

  • • ②有参无返回。

  • • ③无参有返回。

  • • ④有参有返回。

SQL的存储过程也不例外,同样也支持这四种定义,主要依赖于IN、OUT、INOUT三个关键字来区分:

  • • 定义存储过程时,没有入参也没有出参,代表无参无返回类型。

  • • 定义存储过程时,仅定义了带有IN类型的参数,表示有参无返回类型。

  • • 定义存储过程时,仅定义了带有OUT类型的参数,表示无参有返回类型。

  • • 定义存储过程时,同时定义了带有IN、OUT类型的参数,或定义了带有INOUT类型的参数,表示有参有返回类型。

在上述给出的语法体中,最开始有一个DELIMITER $是什么意思呢?

其实这表示指定结束标识,在MySQL中默认是以;分号作为一条语句的结束标识,因此当存储过程的过程体中,如果包含了SQL语句,SQL语句以;结束时,MySQL会认为存储过程的定义也结束了,过程体就会和;结束符冲突,所以一般咱们要重新定义结束符,例如DELIMITER $,表示以$作为结束标识,只有当MySQL识别到$符时,才会认为结束了。

但记得在结束之后,要再次把结束符改回;,即DELIMITER ;

还有一条[...这里在后面讲...]是啥意思呢?这是指定存储过程的约束条件,取值范围有很多,如下:

  • • ①LANGUAGE SQL

  • • ②[NOT] DETERMINISTIC

  • • ③{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }

  • • ④SQL SECURITY { DEFINER | INVOKER }

  • • ⑤COMMENT '....'

是不是看起来很头大?

  • • ①说明存储过程中的过程体是否由SQL语句组成。

  • • ②说明存储过程的返回值是否为固定的,没有[NOT]表示为固定的,默认为非固定的。

  • • ③说明过程体使用SQL语句的限制:

    • • CONTAINS SQL:表示当前存储过程包含SQL,但不包含读写数据的SQL语句。

    • • NO SQL:表示当前存储过程中不包含任何SQL语句。

    • • READS SQL DATA:表示当前存储过程中包含读数据的SQL语句。

    • • MODIFIES SQL DATA:表示当前存储过程中包含写数据的SQL语句。

  • • ④说明哪些用户可以调用当前创建的存储过程:

    • • DEFINER:表示只有定义当前存储过程的用户才能调用。

    • • INVOKER:表示任何具备访问权限的用户都能调用。

  • • ⑤注释信息,可以用来描述当前创建的存储过程。

上述的五条分别和之前的五种取值范围一一对应,一般在写存储过程的时候不会加上这些条件,通常都是使用默认的。

2.1.1、存储过程的定义
-- 查询用户表中的所有信息
select*from`zz_users`;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time       |
+---------+-----------+----------+----------+---------------------+
|1|熊猫|女|6666|2022-08-1415:22:01|
|2|竹子|男|1234|2022-09-1416:17:44|
|3|子竹|男|4321|2022-09-1607:42:21|
|4|黑熊|男|8888|2022-09-1723:48:29|
|8|猫熊|女|8888|2022-09-2717:22:29|
+---------+-----------+----------+----------+---------------------+-- 查询用户表的字段结构
desc`zz_users`;
+---------------+--------------+------+-----+---------+-------+
|Field|Type|Null|Key|Default|Extra|
+---------------+--------------+------+-----+---------+-------+
| user_id       |int(8)|NO| PRI |NULL||
| user_name     |varchar(255)| YES  | MUL |NULL||
| user_sex      |varchar(255)| YES  ||NULL||
| password      |varchar(255)| YES  ||NULL||
| register_time |varchar(255)| YES  ||NULL||
+---------------+--------------+------+-----+---------+-------+

下面会基于这张用户表,来设计几个需求,用来加强对存储过程的掌握度,一共四个案例:

  • • ①定义一个存储过程,查询用户表的所有用户信息。

  • • ②定义一个存储过程,接收一个用户名,查询用户的注册时间。

  • • ③定义一个存储过程,查询ID=1的用户密码并返回。

  • • ④定义一个存储过程,接收一个用户名,返回该用户名对应的用户ID

这四个案例分别对应存储过程的四种类型,也就是分别演示IN、OUT、INOUT的用法。

①查询用户表的所有用户信息
-- 改变结束标识为 $ 符号
DELIMITER $CREATE
-- 定义存储过程的名称为:get_all_userInfo()
PROCEDURE get_all_userInfo()
BEGIN
-- 存储过程体:由一条查询全表的SQL组成
select*from`zz_users`;
-- 标识存储过程体结束
END $
-- 重置结束标识为 ; 符号
DELIMITER ;

上述定义了一个无参无返回的存储过程,接着来执行并调用一下该存储过程,如下:

CALL get_all_userInfo();
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time       |
+---------+-----------+----------+----------+---------------------+
|1|熊猫|女|6666|2022-08-1415:22:01|
|2|竹子|男|1234|2022-09-1416:17:44|
|3|子竹|男|4321|2022-09-1607:42:21|
|4|黑熊|男|8888|2022-09-1723:48:29|
|8|猫熊|女|8888|2022-09-2717:22:29|
+---------+-----------+----------+----------+---------------------+

所有存储过程都是通过CALL命令来调用,调用刚刚定义的存储过程后,显然将用户表的所有用户信息查询出来啦!

②接收一个用户名,查询用户的注册时间

上面定义的存储过程,即没有入参,也没有返回值,接着咱们来演示一下带有入参的存储过程:

-- 这里又将结束标识换成了 // 符号
DELIMITER //CREATE
-- 在定义存储过程时,用 IN 声明了一个入参
PROCEDURE get_user_register_time(IN name varchar(255))
BEGIN
select`register_time`from`zz_users`where`user_name`= name;
END//DELIMITER ;

上面这个存储过程中,使用IN声明了一个入参,其类型为varchar,接着来看看如何调用呢?

CALL get_user_register_time("竹子");
+---------------------+
| register_time       |
+---------------------+
| 2022-09-14 16:17:44 |
+---------------------+

结果十分明显,达到了我们想要的效果,但在定义带有参数的存储过程时要注意:参数名必须在参数类型的前面,同时参数类型需要定义长度,也就是varchar(255),而并非varchar,否则低版本的MySQL会出现不兼容的问题。

③查询ID=1的用户密码并返回

上面掌握了IN的用法,接下来咱们瞅瞅OUT关键字的用法,可以用这个关键字指定返回值:

DELIMITER //CREATE
-- 在定义存储过程时,用 OUT 声明了一个返回值
PROCEDURE get_user_password(OUT userPassword varchar(255))
BEGIN
select`password`into userPassword from`zz_users`where`user_id`=1;
END//DELIMITER ;

在上述这个过程中,当ID=1的用户密码被查询出来之后,会通过into关键字,将查询出的密码赋给userPassword,那这种带有返回值的存储过程又该如何调用呢?如下:

CALL get_user_password(@userPassword);
select @userPassword;
+---------------+
| @userPassword |
+---------------+
|     6666      |
+---------------+

没错,要调用时直接使用@符号,在调用的地方定义变量即可,调用完成后想要查看返回值,还需要手动查询一次调用时定义的变量。

但定义有返回值的存储过程时,有一点也要额外注意:返回值的数据类型一定要和表字段保持一致,否则有可能出现类型转换错误,毕竟不是所有的类型之间可以隐式转换。

④接收一个用户名,返回该用户名对应的用户ID

这个需求有两种实现方式:

  • • ①定义两个参数,一个IN类型的,一个OUT类型的。

  • • ②使用INOUT关键字来实现。

咱们这里主要是为了讲解,因此就采取第二种方式来实现,毕竟还没用过INOUT关键字,如下:

DELIMITER $CREATE
-- 在定义存储过程时,用 OUT 声明了一个返回值
PROCEDURE get_user_id(INOUT parameters varchar(255))
BEGIN
select`user_id`into parameters from`zz_users`where`user_name`= parameters;
END $DELIMITER ;

上述存储过程中,利用INOUT定义了一个参数parameters,在下面的存储过程体当中,即使用它作为查询参数,又使用它作为了保存返回值的变量,再来看看这个函数如何调用:

-- 先定义一个变量
set@parameters="熊猫";
-- 将定义的变量在调用时传入
CALL get_user_id(@parameters);
-- 再次查询定义的变量
select@parameters;
+-------------+
|@parameters|
+-------------+
|1|
+-------------+

如果想要调用这类方法,咱们得先定义一个变量,然后在调用时传入,最后再次查询这个变量即可。

看到这里大家会发现,存储过程中的返回值,并不像正常语言中的return,而是通过变量传递的方式来实现的,上述这个存储过程,就类似于Java中的这段代码:

public void getUserID(Object obj){obj = 1;
}Object obj = new Object("熊猫");
getUserID(obj);
System.out.println(obj);

本质上并没有return出结果,而是对传入的变量重新赋值,从而做到了值的传递。

2.1.2、系统变量和用户变量、局部变量

变量几乎是所有语言都支持的一种语法,存储过程也不例外,在MySQL中大体存在三种级别的变量,即系统变量、用户变量、局部变量,这三个不同级别的有效范围也不同。

系统变量

MySQL启动后,其内部也会存在许多的系统变量,系统的意思是指由MySQL定义的,而并非用户自己定义的,一般系统变量要么来自于MySQL编译期,要么来自于my.ini配置文件。

MySQL的系统变量也会分为两类,一类是全局级变量,一类是会话级变量。

图片

事务隔离级别

当在修改命令中加上global关键字,则代表修改全局级别的系统变量,如若不加或加上session关键字,则表示只修改当前会话的系统变量,这是啥意思呢?修改全局级别表示对所有连接都生效,而修改会话级别的变量,表示只对当前连接生效,在当前连接中修改系统变量的值之后,是不会影响其他数据库连接的。

对于系统变量,想要查看或修改,使用两个@@符号即可,例如:

-- 查看某个系统变量
select @@xxx;
-- 修改某个系统变量
set @@xxx = "xxx";
用户变量

系统变量并非咱们的重点,接着来瞧瞧用户变量,也就是自定义的变量,其实非常简单,如下:

set @变量名称 = 变量值;
select @变量名称;

相较于系统变量而言,用户变量仅仅少了一个@符号而已,不过上述用户变量的赋值,中间的=也可改为:=,其作用也是相同的。除此之外,用户变量的定义还可以和SQL组合,如下:

-- 将用户表的总行数赋值给 row_count 变量
select @row_count := count(*) from `zz_users`;
-- 将 user_id 的平均值赋给 avg_user_id 变量
select avg(user_id) into @avg_user_id from `zz_users`;

上述这两种方式,都可以创建一个用户变量,也就意味着但凡出现@符号时,MySQL都会将其识别为在定义变量。

局部变量

前面简单讲述了用户变量,但在存储过程中还存在一种名为局部变量的概念,这也就是类似于在Java的方法中定义的变量,有效范围只对当前方法体生效,而局部变量亦是同理,只对当前存储过程体有效,其他存储过程或外部是无法读取或操作局部变量的,定义方式如下:

DECLARE 变量名称 数据类型 default 默认值;

这跟通过SQL创建表时,声明表字段的语法相差无几,就前面多了一个DECLARE关键字,举个简单的例子,如下:

DECLARE message varchar(255) default "not message";

上述定义了一个名为message的局部变量,如果后续使用时未对其赋值,该变量的默认值为"not message"

后续使用局部变量时,主要有两种赋值方式,如下:

-- 赋值方式一
SET message = 变量值;
SET message := 变量值;-- 赋值方式二
select 字段名或函数 into message from 表名;

非常的简单,接着来结合存储过程一起熟悉一下用户变量和局部变量:

DELIMITER //CREATE
-- 定义了一个 求两数之和 的存储过程
PROCEDURE add_value(IN number1 int(8),OUTresultint(8))
BEGIN
-- 这里定义了一个局部变量:number2,默认值为 666
DECLARE number2 int(8)default666;
-- 将两个数字相加,计算得到的和放入用户变量 result 中
SETresult:= number1 + number2;
END//DELIMITER ;-- 定义一个用户变量,接收调用存储过程后得到的和
SET@result=0;
-- 调用存储过程,传入一个数字 888 以及接收结果的 result 变量
CALL add_value(888,@result);
-- 查询计算后的和
SELECT@result;
+---------+
|@result|
+---------+
|1554|
+---------+

对于局部变量的定义,必须要写在BEGIN、END之间,否则会提示语法错误,这一点需要在使用时注意,上述定义的存储过程,其工作十分简单,即计算两数之和并返回,具体的过程参考源码中的注释,经过这个例子相信大家对存储过程中的变量能够进一步掌握。

2.1.3、流程控制 - IF判断与CASE分支

上面简单掌握几种变量的语法后,接着再来说说存储过程中的流程控制,也就是条件判断、循环、跳转等语法,先来聊一聊所有语言都有的条件判断。

条件判断:IF

在存储过程中,主要有两类条件判断的语法,即IF、CASE,先来说说常见的IF,语法如下:

IF 条件判断 THEN-- 分支操作.....
ELSEIF 条件判断 THWN-- 分支操作.....
ELSE-- 分支操作.....
END IF

上述这段if判断语句基本上和其他语言中相差无几,当一个条件判断成立时,就会进入相应的分支中执行,否则程序会跳过该分支继续往下执行,举个案例快速过一下:

DELIMITER $CREATE
PROCEDURE if_user_age(IN age int,OUT msg varchar(255))
BEGINIF age <18THEN
SET msg :='未成年';ELSEIF age =18THEN
SET msg :='刚成年';
ELSE
SET msg :='已成年';
END IF;END $DELIMITER ;

上面定义了一个判断年龄的存储过程,调用时需要传入一个age值,内部会对传入的值进行判断,最后将判断结果写入到msg变量中,调用方式如下:

SET @msg:="Not Data";CALL if_user_age(16,@msg);
SELECT@msg;
+---------+
|@msg|
+---------+
|未成年|
+---------+CALL if_user_age(18,@msg);
SELECT@msg;
+---------+
|@msg|
+---------+
|刚成年|
+---------+CALL if_user_age(25,@msg);
SELECT@msg;
+---------+
|@msg|
+---------+
|已成年|
+---------+

OK~,存储过程中分支判断的语法,与常规编程语言中的if、else if、else无太大差异,接着来看看CASE的语法。

分支判断:CASE

存储过程中的CASE语法,就类似于Java中的switch语法,但CASE有两种写法,如下:

-- 第一种语法
CASE变量
WHEN值1THEN
-- 分支操作1....
WHEN值2THEN
-- 分支操作2....
.....
ELSE
-- 分支操作n....
ENDCASE;-- 第二种语法
CASE
WHEN条件判断1THEN
-- 分支操作1....
WHEN条件判断2THEN
-- 分支操作2....
.....
ELSE
-- 分支操作n....
ENDCASE;

举个例子快速过一下,这里就演示第一种语法,毕竟第二种方式就等同于多重IF判断:

DELIMITER $CREATE
PROCEDURE test_case(IN n int)
BEGIN
CASE n
WHEN1THEN
select'竹子真的帅';
WHEN2THEN
select'熊猫真好看';
ELSE
select'两个都好看';
ENDCASE;
END $DELIMITER ;

调用结果如下(上面的SELECT 'xxx'就类似于其他语言的print输出):

CALL test_case(1);
+---------------+
|竹子真的帅|
+---------------+
|竹子真的帅|
+---------------+CALL test_case(2);
+---------------+
|熊猫真好看|
+---------------+
|熊猫真好看|
+---------------+CALL test_case(3);
+---------------+
|两个都好看|
+---------------+
|两个都好看|
+---------------+

调用结果很明显,基本上与Java中的switch效果相同!OK~,接着来看看存储过程中的循环语法。

2.1.4、循环:LOOP、WHILE、REPEAT

编程中常见的循环有for、foreach、while、do-while四大类,而存储过程中也支持LOOP、WHILE、REPEAT三类循环,接着挨个简单过一下。

LOOP循环

先简单聊一聊LOOP,语法如下:

循环名称:LOOP-- 循环体....
END LOOP 循环名称;

在存储过程的循环,与其他编程语言的循环并不同,在存储过程中可以给每个循环取一个名字,后续可以基于这个名字来跳出循环,但如果想要跳出一个循环,还需要结合LEAVE这个关键字,否则会令循环成为一个死循环,无限执行下去,现在先上个简单的例子:

DELIMITER $CREATE PROCEDURE test_loop(IN num int)
BEGIN
--定义一个局部变量:numDECLARE num int(8)default1;add_loop:LOOP
--一直循环对num +1SET num = num +1;
--当num被加到100时IF num >=100 THEN
--跳出循环LEAVE add_loop;
END IF;
END LOOP add_loop;select num;
END $DELIMITER ;

这个存储过程很简单,就是利用LOOP循环对局部变量num进行累加,加到100时退出循环,最后查询一下num值,调用结果如下:

CALL test_loop();
+--------+
|  num   |
+--------+
|  100   |
+--------+

效果跟想象的差不多,num最终值为100,OK,接着看看其他的循环。

WHILE循环

WHILE循环的语法也和前面的LOOP循环类似,如下:

【循环名称】:WHILE 循环条件 DO-- 循环体....
END WHILE 【循环名称】;

其实对于这个循环名称,可以写也可以不写,主要是利用名称来做跳转,这点后续说,先来举个简单的例子感受一下WHILE循环:

DELIMITER $CREATE
PROCEDURE test_while()
BEGIN
-- 定义一个局部变量:num
DECLARE num int(8)default1;-- 循环对num=1,当<=10时退出WHILE num <=10 DO
-- 一直循环对num + 1
SET num = num +1;
END WHILE;-- 最后查询一下num值
SELECT num;
END $DELIMITER ;

这个存储过程也是一个对num变量不断做+1的循环,调用结果如下:

CALL test_while();
+-------+
|  num  |
+-------+
|  11   |
+-------+

但此时来看,答案为何是11而并不是10呢?因为循环条件是num<=10,也就是当num=10的时候,依旧会循环一次,num又被+1,所以最终num=11

REPEAT循环

REPEAT循环和之前两个循环不同,在这种循环中,有专门控制循环结束的语法,如下:

【循环名称】:REPEAT-- 循环体....
UNTIL 结束循环的条件判断
END REPEAT 【循环名称】;

上述语法种,当UNTIL关键字之后的条件为真时,循环就会终止,来看个用例:

DELIMITER $CREATE
PROCEDURE test_repeat()
BEGIN
-- 定义一个局部变量:num
DECLARE num int(8)default1;REPEAT
SET num = num +1;UNTIL num>=10
END REPEAT;-- 最后查询一下num值
SELECT num;
END $DELIMITER ;

存储过程和之前的循环案例相同,也就是对num做累加,调用结果如下:

CALL test_repeat();
+-------+
|  num  |
+-------+
|  10   |
+-------+

此时num=10是啥原因呢?因为当num>=10时会停止循环,当num被累加到10后就触发了终止条件,因此最终num=10

接着来看看存储过程中的跳转语法,其实在之前的LOOP循环中简单的用过。

2.1.5、跳转:LEAVE、ITERATE

LEAVE、ITERATE两个跳转的关键字,其实本质上就和Java中的break、continue类似,LEAVE主要功能是结束循环体,当执行循环体时遇到了LEAVE关键字,就会结束当前循环。而ITERATE则是跳出本次循环,继续下次循环的意思,作用与continue相同,接着举个案例来简单的实验一下。

-- 测试LEAVE关键字终止循环
DELIMITER $CREATE
PROCEDURE test_leave()
BEGIN
-- 定义一个局部变量:num
DECLARE num int(8)default1;add_while:WHILE TRUE DO
-- 对num持续做+1
SET num = num +1;-- 如果num=10,用LEAVE终止循环IF num =10THENLEAVE add_while;
END IF;END WHILE add_while;-- 最后查询一下num值
SELECT num;
END $DELIMITER ;

依旧还是这个令人熟悉的例子,在这里咱们通过WHILE TRUE的方式开启了一个死循环,后续利用LEAVE来终止循环,调用结果如下:

CALL test_leave();
+-------+
|  num  |
+-------+
|  10   |
+-------+

最终num=10,这是因为当num=10的时候,就会执行LEAVE add_while;语句,会通过LEAVE关键字终止名为add_while的循环。

存储过程中的这点设计的很不错,在编写循环结构时,由于可以为每个循环命名,因此后续要跳出、终止某个循环时,可以直接通过循环的名称来跳转,在循环嵌套的情况下,这点尤为好用!

-- 测试ITERATE关键字跳出循环
DELIMITER $CREATE
PROCEDURE test_iterate()
BEGIN
-- 定义一个局部变量:num
DECLARE num int(8)default0;
-- 定义一个局部变量用来统计偶数和
DECLARE even_sum int(8)default0;sum_while:WHILE num <=100 DO
-- 对num持续做+1
SET num = num +1;-- 如果num=10,用LEAVE终止循环IF num %2=0THEN
SET even_sum = even_sum + num;
ELSE
-- 如果num不为偶数则跳过本次循环ITERATE sum_while;
END IF;END WHILE sum_while;-- 最后查询一下偶数之和
SELECT even_sum;
END $DELIMITER ;

上述这个存储过程作用也很简单,首先开启一个循环遍历1~100,接着对100以内的所有偶数求和,如果num%2=0则表示为偶数,将结果累加到even_sum变量中,不为0则通过ITERATE关键字跳出本次循环,继续下次循环,最终调用结果如下:

+------------+
|  even_sum  |
+------------+
|   2550     |
+------------+

结果确实达到了咱们想要的效果!100内的偶数之和确实为2550

2.1.6、存储过程的游标

游标是所有数据库的存储过程中,很重要的一种特性,它可以对一个结果集中的数据按条处理,也就意味着原本查询出的数据是一个整体性质的集合,而使用游标可以对该集合中的数据逐条处理,在使用游标时一般都会遵循下述四步:

-- ①声明(创建)游标
DECLARE游标名称CURSORFORselect...;-- ②打开游标
OPEN游标名称;-- ③使用游标
FETCH游标名称INTO变量名称;-- ④关闭游标
CLOSE 游标名称;

观察游标的声明语法应该会发现,它和创建局部变量的方式类似,但后面会加上CURSOR FOR关键字来表明创建的是游标,接着来个简单的案例感受一下游标的使用,需求如下:

计算用户表中user_id最大的前N个奇数ID之和。

DELIMITER //CREATE
PROCEDURE id_odd_number_sum(IN N int(8),OUT sum int(8))
BEGIN
-- 声明局部变量:
--   uid:用于记录每一个user_id
--  odd_id_count:记录奇数ID的个数
--  odd_id_sum:记录奇数ID的和
DECLARE uid int(8)DEFAULT0;
DECLARE odd_id_count int(8)DEFAULT0;
DECLARE odd_id_sum int(8)DEFAULT0;-- 声明一个游标:存储倒序的user_id结果集
DECLARE uid_cursor CURSORFORselect user_id from zz_users orderby user_id desc;-- 打开游标
OPEN uid_cursor;-- 使用游标REPEAT
-- 将游标中的每一条user_id值,赋给user_id变量
FETCH uid_cursor INTO uid;-- 如果当前user_id是奇数,则将ID值累加到sum中IF uid %2!=0THEN
SET odd_id_count = odd_id_count +1;
SET odd_id_sum = odd_id_sum + uid;
END IF;
-- 根据传入的N来决定循环的次数UNTIL odd_id_count >= N END REPEAT;-- 将前N个奇数ID之和赋给外部变量:sum
SET sum = odd_id_sum;
-- 关闭游标
CLOSE uid_cursor;
END//DELIMITER ;

代码的具体逻辑参考上述注释,最终调用结果如下:

select *from zz_users;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time       |
+---------+-----------+----------+----------+---------------------+
|1|熊猫|女|6666|2022-08-1415:22:01|
|2|竹子|男|1234|2022-09-1416:17:44|
|3|子竹|男|4321|2022-09-1607:42:21|
|4|黑熊|男|8888|2022-09-1723:48:29|
|8|猫熊|女|8888|2022-09-2717:22:29|
+---------+-----------+----------+----------+---------------------+CALL id_odd_number_sum(2,@sum);
select@sum;
+------+
|  num |
+------+
|4|
+------+

表中总共存在1、3这两个奇数ID,然后调用存储过程时传入2,表示获取前两个奇数ID之和,最终结果为4(1+3),显然达到了咱们的需求,在这个过程中我们利用了游标对order by之和的结果集,其中的每个user_id进行了逐条处理、逐条判断,从而完成了前面给出的需求。

2.1.7、存储过程语法小结

2.2、客户端如何调用存储过程

一般在Java项目中,都会选择MyBatis作为操作数据库的ORM框架,那在其中调用存储过程的方式也很简单,如下:

<parameterMap type="根据存储过程决定" id="自己命名"> <parameter property="存储过程参数1" jdbcType="数据类型" mode="IN"/><parameter property="存储过程参数2" jdbcType="数据类型" mode="IN"/><parameter property="存储过程参数3" jdbcType="数据类型" mode="OUT"/>
</parameterMap><insert id="和Dao接口的方法同名" parameterMap="上面的ID值" statementType="CALLABLE"> {call 存储过程名(?, ?, ?)} 
</insert >

当需要调用存储过程中,只需要调用该xml对应的Dao/Mapper层接口即可。

2.3、存储过程的管理

所谓的存储过程管理,也就是指存储过程的查看、修改和删除,在MySQL中也提供了一系列命令,以便于咱们完成这些工作,如下:

  • • SHOW PROCEDURE STATUS;:查看当前数据库中的所有存储过程。

  • • SHOW PROCEDURE STATUS WHERE db = '库名' AND NAME = '过程名';:查看指定库中的某个存储过程。

  • • SHOW CREATE PROCEDURE 存储过程名;:查看某个存储过程的源码。

  • • ALTER PROCEDURE 存储过程名称 ....:修改某个存储过程的特性。

  • • DROP PROCEDURE 存储过程名;:删除某个存储过程。

当然,也可以通过下述命令来查看某张表的存储过程:

-- 查看某张表的所有存储过程
select*from表名.Routineswhere routine_type ="PROCEDURE";-- 查看某张表的某个存储过程
select*from表名.Routineswhere routine_name ="过程名"AND routine_type ="PROCEDURE";

2.4、存储过程的应用场景

存储过程到底该不该用,这点在《阿里开发手册》中是强制禁止使用的:

图片

阿里开发规范

因为存储过程难以维护,同时拓展性和移植性都很差,因此大多数的开发规范中都会明令禁止使用,但存储过程能够带来的优势也极为明显。

①插入测试数据时,一般为了测试项目,都会填充测试数据,往常是写Java-for跑数据,但现在可以用存储过程来批量插入,它的效率会比用for循环快上无数倍,毕竟从Java传递SQL需要时间,拿到SQL后还要经过解析、优化....一系列工作,而用存储过程则不会有这些问题。

②对数据做批处理时,也可以用存储过程来跑,比如将一个表中的数据洗到另外一张表时,就可以利用存储过程来处理。

③一条SQL无法完成的、需要应用程序介入处理的业务,尤其是组成起来SQL比较长时,也可以编写一个存储过程,然后客户端调用即可。

三、MySQL的触发器

触发器本质上是一种特殊的存储过程,但存储过程需要人为手动调用,而触发器则不需要,它可以在执行某项数据操作后自动触发,就类似于Spring-AOP中的切面一样,当执行了某个操作时就会触发相应的切面逻辑。

但触发器是在MySQL5.0.2版本以后才开始被支持的,在此之前的MySQL并不能创建触发器,而触发器的触发条件是以事件为单位的,对于事件相信诸位一定不陌生,比如前端的按钮标签,就会经常用到它的点击事件,当用户点击某个按钮后,就会触发对应的点击函数,从而执行相应逻辑,而MySQL触发器亦是同理。

创建一个触发器的语法如下:

CREATE TRIGGER触发器名称
{BEFORE | AFTER}{INSERT|UPDATE|DELETE}ON表名
FOREACHROW-- 触发器的逻辑(代码块);

从上述语法结构可以看出,对于每一个触发器而言,总共有插入、修改以及删除三种触发事件可选,同时也可以选择将触发器放在事件开始前,亦或事件结束后执行,这点几乎和AOP切面的切入点一模一样,同时也要记住:每个触发器创建后,必然是附着在一张表上的,因为在创建触发器的时候必须要指定表名,它会监控这张表上发生的事件,比如举个例子:

当我对zz_users表创建了一个插入事件的后置处理器时,那么当每次表中插入数据后,都会自动触发一次相应的逻辑。

接下来依旧上个小案例,简单的感受一下触发器的用法,当然,依旧不要在乎需求是否合理,主要是感受触发器的用法:

-- 创建一张注册日志表
CREATETABLE`register_log`(
-- 注册时间
`register_time`varchar(255),
-- 注册地
`register_address`varchar(255),
-- 注册设备
`register_facility`varchar(255)
)
ENGINE =InnoDB
CHARACTERSET= utf8 
COLLATE= utf8_general_ci 
ROW_FORMAT =Compact;-- 在用户表上创建一个触发器
DELIMITER //CREATETRIGGER zz_users_insert_beforeBEFORE INSERTON zz_users
FOREACHROW
BEGIN
insertinto`register_log`values(NOW(),"北京市海淀区","IOS");
END//DELIMITER ;

上述案例中,对zz_users用户表建立了一个插入后置触发器,也就是当用户表中插入一条数据时,会向注册日志表中自动添加一条注册日志,测试效果如下:

-- 向用户表插入一条用户记录
INSERTINTO`zz_users`VALUES(9,"棕熊","男","0369","2022-10-17 23:48:29");-- 查询注册日志表
select*from`register_log`;
+---------------------+--------------------+-------------------+
| register_time       | register_address   | register_facility |
+---------------------+--------------------+-------------------+
|2022-10-1818:52:50|北京市海淀区| IOS               |
+---------------------+--------------------+-------------------+

刚刚并未手动插入注册日志表的数据,但会发现:当用户表中插入一条数据时,会自动触发创建的插入后置触发器,这个效果相信了解过切面编程的小伙伴都不陌生。

但此时有个小问题,我们可以为一张表的某个事件创建触发器,但在触发器中有没有办法拿到当前操作的数据呢?比如在insert事件中能否拿到插入的数据?update事件中能否拿到修改前后的数据?答案是可以的,在触发器中有NEW、OLD这两个关键字。

3.1、触发器的NEW、OLD关键字

在触发器中,NEW表示新数据,OLD表示老数据,各类型的事件如下:

  • • insert插入事件:NEW表示当前插入的这条行数据。

  • • update修改事件:NEW表示修改后的新数据,OLD表示修改前的老数据。

  • • delete删除事件:OLD表示删除前的老数据。

这样似乎有些令人犯迷糊呀,举个简单的例子,以用户表的修改事件为例:

-- 执行的修改语句
update`zz_users`set user_name ="粉熊"and user_sex ="女"where user_id =9;-- 用户表修改事件的触发器
DELIMITER //CREATETRIGGER zz_users_update_beforeBEFORE UPDATEON zz_users
FOREACHROW
BEGIN
DECLARE new_name varchar(255);
DECLARE old_name varchar(255);-- 可以通过 NEW 关键字拿到修改后的新数据(粉熊)
SET new_name := NEW.user_name;-- 可以通过 OLD 关键字拿到修改前的老数据(棕熊)
SET old_name := OLD.user_name;
END//DELIMITER ;

经过上述这个例子之后,大家应该更能理解NEW、OLD两个关键字,由于这两个关键字存储,它能够使触发器更为灵活。

为啥说触发器是一种特殊的存储过程呢?因为本质上触发器中所用的语法,和存储过程完全是一模一样的,只是存储过程需要手动调用,而触发器则是根据事件自动触发。

触发器的可以用于一些特殊的业务场景,比如需要在写数据前做数据安全性检测、又或者是洗数据时需要效验数据完整性、正确性、又或者是数据的备份和同步等这类需求。

3.2、触发器的管理

  • • SHOW TRIGGERS;:查看当前数据库中定义的所有触发器。

  • • SHOW CREATE TRIGGER 触发器名称;:查看当前库中指定名称的触发器。

  • • SELECT * FROM information_schema.TRIGGERS;:查看MySQL所有已定义的触发器。

  • • DROP TRIGGER IF EXISTS 触发器名称;:删除某个指定的触发器。

MySQL命令大全

一、基础操作与库命令

MySQL中的所有命令默认是以;分好结尾的,因此在执行时一定要记得带上分号,否则MySQL会认为你这条命令还未结束,会继续等待你的命令输入。

1.1、MySQL基础操作命令

  • • net start mysqlWindows系统启动MySQL服务。

  • • 安装目录/mysql startLinux系统启动MySQL服务。

    • • shutdown:后面的start换成这个,表示关闭MySQL服务。

    • • restart:换成restart表示重启MySQL服务。

  • • ps -ef | grep mysqlLinux查看MySQL后台进程的命令。

  • • kill -9 MySQL进程ID:强杀MySQL服务的命令。

  • • mysql -h地址 -p端口 -u账号 -p:客户端连接MySQL服务(需要二次输入密码)。

  • • show status;:查看MySQL运行状态。

  • • SHOW VARIABLES like %xxx%;:查看指定的系统变量。

  • • show processlist;:查看当前库中正在运行的所有客户端连接/工作线程。

  • • show status like "Threads%";:查看当前数据库的工作线程系统。

  • • help data types;:查看当前版本MySQL支持的所有数据类型。

  • • help xxx:查看MySQL的帮助信息。

  • • quit:退出当前数据库连接。

1.2、MySQL库相关的命令

  • • show databases;:查看目前MySQL中拥有的所有库。

  • • show engines;:查看当前数据库支持的所有存储引擎。

  • • use 库名;:使用/进入指定的某个数据库。

  • • show status;:查看当前数据库的状态信息。

  • • show grants;:查看当前连接的权限信息。

  • • show errors;:查看当前库中记录的错误信息。

  • • show warnings:查看当前库抛出的所有警告信息。

  • • show create database 库名;:查看创建某个库的SQL详细信息。

  • • show create table 表名;:查看创建某张表的SQL详细信息。

  • • show tables;:查看一个库中的所有表。

  • • desc 表名;:查看一张表的字段结构。除开这种方式还有几种方式:

    • • describe 表名;:查看一张表的字段结构。

    • • show columns from 表名;:查看一张表的字段结构。

    • • explain 表名;:查看一张表的字段结构。

  • • create database 库名;:新建一个数据库,后面还可以指定编码格式和排序规则。

  • • drop database 库名;:删除一个数据库。

  • • ALTER DATABASE 库名 DEFAULT CHARACTER SET 编码格式 DEFAULT COLLATE 排序规则:修改数据库的编码格式、排序规则。

1.3、MySQL表相关的命令

对于MySQL表相关的命令,首先来聊一聊创建表的SQL命令,如下:

CREATE TABLE `库名`.`表名`  (字段名称1 数据类型(精度限制) [字段选项],字段名称2 数据类型(精度限制) [字段选项]
) [表选项];

对于表中的每个字段,都需要用,分割,但最后一个字段后面无需跟,逗号,同时创建表时,对于每个字段都有多个字段选项,对于一张表而言也有多个表选项,下面一起来看看。

  • • 字段选项(可以不写,不选使用默认值):

    • • NULL:表示该字段可以为空。

    • • NOT NULL:表示改字段不允许为空。

    • • DEFAULT 默认值:插入数据时若未对该字段赋值,则使用这个默认值。

    • • AUTO_INCREMENT:是否将该字段声明为一个自增列。

    • • PRIMARY KEY:将当前字段声明为表的主键。

    • • UNIQUE KEY:为当前字段设置唯一约束,表示不允许重复。

    • • CHARACTER SET 编码格式:指定该字段的编码格式,如utf8

    • • COLLATE 排序规则:指定该字段的排序规则(非数值类型生效)。

    • • COMMENT 字段描述:为当前字段添加备注信息,类似于代码中的注释。

  • • 表选项(可以不写,不选使用默认值):

    • • ENGINE = 存储引擎名称:指定表的存储引擎,如InnoDB、MyISAM等。

    • • CHARACTER SET = 编码格式:指定表的编码格式,未指定使用库的编码格式。

    • • COLLATE = 排序规则:指定表的排序规则,未指定则使用库的排序规则。

    • • ROW_FORMAT = 格式:指定存储行数据的格式,如Compact、Redundant、Dynamic....

    • • AUTO_INCREMENT = n:设置自增列的步长,默认为1

    • • DATA DIRECTORY = 目录:指定表文件的存储路径。

    • • INDEX DIRECTORY = 目录:指定索引文件的存储路径。

    • • PARTITION BY ...:表分区选项,后续讲《MySQL表分区》再细聊。

    • • COMMENT 表描述:表的注释信息,可以在这里添加一张表的备注。

整体看下来会发现选项还蛮多,下面贴个例子感受一下:

-- 在 db_zhuzi 库下创建一张名为 zz_user 的用户表
CREATETABLE`db_zhuzi`.`zz_user`(
-- 用户ID字段:int类型、不允许为空、设为自增列、声明为主键
`user_id`int(8)NOTNULL AUTO_INCREMENT PRIMARY"i_p_id" COMMENT '用户ID',
-- 用户名称字段:字符串类型、运行为空、默认值为“新用户”
`user_name`varchar(255)NULLDEFAULT"新用户" COMMENT '用户名'
)
-- 存储引擎为InnoDB、编码格式为utf-8、字符排序规则为utf8_general_ci、行格式为Compact
ENGINE =InnoDB
CHARACTERSET= utf8 
COLLATE= utf8_general_ci 
ROW_FORMAT =Compact;

其他关于表操作的SQL命令

  • • show table status like 'zz_users'\G;:纵排输出一张表的状态信息。

  • • alter table 表名 表选项;:修改一张表的结构,如alter table xxx engine=MyISAM

  • • rename table 表名 to 新表名;:修改一张表的表名。

  • • alter table 表名 字段操作;:修改一张表的字段结构,操作如下:

    • • add column 字段名 数据类型:向已有的表结构添加一个字段。

    • • add primary key(字段名):将某个字段声明为主键。

    • • add foreing key 外键字段 表名.字段名:将一个字段设置为另一张表的外键。

    • • add unique 索引名(字段名):为一个字段创建唯一索引。

    • • add index 索引名(字段名):为一个字段创建普通索引。

    • • drop column 字段名:在已有的表结构中删除一个字段。

    • • modify column 字段名 字段选项:修改一个字段的字段选项。

    • • change column 字段名 新字段名:修改一个字段的字段名称。

    • • drop primary key:移除表中的主键。

    • • drop index 索引名:删除表中的一个索引。

    • • drop foreing key 外键:删除表中的一个外键。

  • • drop table if exists 表名:如果一张表存在,则删除对应的表。

  • • truncate table 表名:清空一张表的所有数据。

  • • create table 表名 like 要复制的表名:复制一张表的结构,然后创建一张新表。

  • • create table 表名 as select * from 要复制的表名:同时复制表结构和数据创建新表。

1.4、表的分析、检查、修复与优化操作

MySQL本身提供了一系列关于表的分析、检查与优化命令:

  • • ①分析表:分析表中键的分布,如主键、唯一键、外键等是否合理。

  • • ②检查表:检查表以及表的数据文件是否存在错误。

  • • ③修复表:当一个表的数据或结构文件损坏时,可以修复表结构(仅支持MyISAM表)。

  • • ④优化表:消除delete、update语句执行时造成的空间浪费。

分析表

语法如下:

analyze [local | no_write_to_binlog] table 表名1;

其中的可选参数local、no_write_to_binlog代表是否将本条SQL记录进bin-log日志,默认情况下是记录的,加上这两个参数中的其中一个后则不会记录,执行效果如下:

图片

分析表

如果Msg_text显示的是OK,则代表这张表的键不存在问题,存在问题的情况我这边就不模拟了,后面举例聊。

检查表

语法如下:

check table 表名1,表名2... [检查选项];

分析、检查、优化、修复的命令都支持同时操作多张表,不同的表之间只需用,逗号隔开即可。检查命令有多个可选项,如下:

  • • quick:不扫描行数据,不检查链接错误,仅检查表结构是否有问题。

  • • fast:只检查表使用完成后,是否正确关闭了表文件的FD文件描述符。

  • • changed:从上述检查过的位置开始,只检查被更改的表数据。

  • • medium:检查行数据,收集每一行数据的键值(主键、外键...),并计算校验和,验证数据是否正确。

  • • extended:对每行数据的所有字段值进行检查,检查完成后可确保数据100%正确。

先来看看执行结果吧,如下:

图片

检查表

这回的结果出现了些许不同,Msg_text中出现了一个Error信息,提示咱们检查的zz_u表不存在,而对于一张存在的zz_users表,则返回OK,表示没有任何问题。

修复表

语法如下:

repair [local | no_write_to_binlog] table 表名 [quick] [extended] [use_frm];

值得一提的是,修复表的命令不支持InnoDB引擎,仅支持MyISAM、CSV、引擎,比如基于InnoDB引擎的表执行修复命令时,提示如下:

图片

修复表

上述Msg_text信息翻译过来的意思是:选择的表其引擎并不支持修复命令。

InnoDB引擎其实也有修复机制,可以在my.ini/my.conf文件中加一行配置:[mysqld]innodb_force_recovery = 1,这样在启动时会强制恢复InnoDB的数据。

上述这个修复机制默认是不开启的,因为InnoDB不需要这个恢复机制,InnoDB有完善的事务和持久化机制,客户端提交的事务都会持久化到磁盘,除非你人为损坏InnoDB的数据文件,否则基本上不会出现InnoDB数据损坏的情况。

优化表

语法如下:

optimize [local | no_write_to_binlog] table 表名;

这里值得一提的是:此优化非彼优化,并不意味着你的表存在性能问题,执行后它会自动调优,而是指清除老数据,执行效果如下:

图片

优化表

其实删除一条数据本质上并不会立马从磁盘移除,而是会先改掉隐藏的删除标识位,执行这条优化命令后,MySQL会将一些已经delete过的数据彻底从磁盘删除,从而释放这些“废弃数据”占用的空间。

上面的执行结果显示:“目前表的数据已经是最新的了”,这是啥原因呢?因为我这张表中压根没有数据,没有插入过数据,自然也不会有删除数据的动作,因此就会出现这个提示。

其实这几个功能,在mysqlcheck工具中也有提供。

1.5、MySQL忘记密码怎么办?

**MySQL忘记密码怎么办**?对于这种情况其实也十分常见,哪忘记时该如何处理呢?可以重置密码。

①先停掉MySQL的后台服务:

  • • Windows系统请执行:net stop mysql

  • • Linux系统请执行:安装目录/mysql shutdownkill强杀进程也可以)

②进入到MySQL安装目录下的bin文件夹内,执行mysqld --skip-grant-tables去掉连接认证。

③因为上面关掉了连接认证,接着输入mysql敲下回车,进入mysql终端命令行。

④输入use mysql;,进入MySQL自身的系统数据库,然后输入show tables;查看所有表。

⑤查询MySQL中注册的所有用户:select user,host,password from user;

⑥使用update语句,更改root超级管理员的账号密码,如下:

update user set password=password('123') where user="root" and host="localhost";

因为MySQL本身会用一张用户表来存储所有已创建的账号信息,连接时的效验基准也是来自于该表中的数据,因此在这里修改密码后,再用新密码登录即可!

如果不是root账号的密码忘记了,则可以直接登录root账号修改其他用户的密码,如果是root账号则按照上述流程操作。

完成之后可以用mysql -uroot -p123连接一下,测试密码是否被重置。

二、增删改查语句

2.1、基本的增删改查语句

插入数据

增删改查俗称为CRUD,这也是MySQL运行之后执行次数最多的一类SQL语句,同时也是每位开发者写的最多的SQL语句,接下来则说说这块的语句,首先登场的是咱们的几位老伙伴,即insert、delete、update、select...这类普通SQL语句。

  • • insert into 表名(字段名...) values(字段值...);:向指定的表中插入一条数据。

  • • insert into 表名(字段名...) values(字段值...),(...)...;:向表中插入多条数据。

  • • insert into 表名 set 字段名=字段值,...;:插入一条数据,但只插入某个字段的值。

如果要插入一条完整的数据,字段名可以用*代替所有字段,除开上述两种插入数据的基本方式外,还有几种批量插入的方式,如下:

-- 使用insert语句批量插入另一张表中查询的数据
insert into 表名(字段名...) select 字段名... from 表名...;-- 使用replace语句来实现批量插入
replace into 表名(字段名1,字段名2...) values(字段值....),(字段值...),...;

上述批量插入数据的方式中,还可以通过replace关键字来实现插入,它与insert有啥区别呢?答案在于它可以实现批量更新,使用replace关键字来插入数据的表必须要有主键,MySQL会根据主键值来决定新增或修改数据,当批量插入的数据中,主键字段值在表中不存在时,则会向表中插入一条相应的数据,而当插入数据中的主键值存在时,则会使用新数据覆盖原有的老数据。

删除数据
  • • delete from 表名;:删除一张表的所有数据。

  • • delete from 表名 where 条件;:根据条件删除一条或多条数据。

  • • truncate table 表名:清空一张表的所有数据。

修改数据
  • • update 表名 set 字段名=字段值,...;:修改表中所有记录的数据。

  • • update 表名 set 字段名=字段值,... where 条件;:根据条件修改一条或多条记录的数据。

  • • replace 表名(字段名1,...) values(字段值...),...;:批量修改对应主键记录的数据。

查询数据
  • • select * from 表名;:查询一张表的所有数据。

  • • select * from 表名 where 条件;:根据条件查询表中相应的数据。

  • • select 字段1,字段2... from 表名 where 条件;:根据条件查询表中相应数据的指定字段。

  • • select 函数(字段) from 表名;:对查询后的结果集,进行某个函数的特殊处理。

上述三种是最基本的查询方式,接着来看一些高级查询语法,如下:

-- 为查询出来的字段取别名
select字段1as别名,...from表名where条件;
select字段1别名,...from表名;-- 为查询出的表取别名
select*from表名as别名;-- 以多条件查询数据
select*from表名where字段1=值1and字段2=值2and...;-- 所有条件都符合时才匹配
select*from表名where字段1=值1or字段2=值2or...;-- 符合任意条件的数据都会返回
-- =符号,可以根据情况换为>、<、>=、<=、!=、between and、is null、not is null这些-- 对查询后的结果集使用函数处理
select函数(字段)from表名where条件;-- 对查询条件使用函数处理
select*from表名where函数(条件);-- 模糊查询
select*from表名where字段like"%字符";-- 查询字段值以指定字符结尾的所有记录
select*from表名where字段like"字符%";-- 查询字段值以指定字符开头的所有记录
select*from表名where字段like"%字符%";-- 查询字段值包含指定字符的所有记录-- 按照多值查询对应行记录
select*from表名where字段in(值1,值2,...);
-- 按照多值查询相反的行记录
select*from表名where字段notin(值1,值2,...);
-- 基于多个字段做多值查询
select*from表名where(字段1,字段2...)in((值1,值2,...),(...),...);-- 只需要查询结果中的前N条数据
select*from表名 limit N;
-- 返回查询结果中 N~M 区间的数据
select*from表名 limit N,M;-- 联合多条SQL语句查询(union all表示不去重,union表示对查询结果去重)
select*from表名where条件
unionall
select*from表名where 条件;
分组过滤、数据排序

SQL语句时,有些需求往往无法通过最基本的查询语句来实现,因此就需要用到一些高级的查询语法,例如分组、过滤、排序等操作,接着先聊聊这个,语法如下:

-- 基于一个字段进行排序查询
select*from表名orderby字段名asc;-- 按字段值正序返回结果集
select*from表名orderby字段名desc;-- 按字段值倒序返回结果集
select*from表名orderby字段1asc,字段2desc;-- 按照多字段进行排序查询-- 基于字段进行分组
select*from表名groupby字段1,字段2....;-- 基于分组查询后的结果做条件过滤
select*from表名groupby字段1having 条件;

实际上group by、having这些语句,更多的要配合一些聚合函数使用,如min()、max()、count()、sum()、avg()....,这样才能更符合业务需求,但对于聚合函数后面再介绍,先简单说说where、having的区别:

这两个关键字都是用来做条件过滤的,但where优先级会比group by高,因此当分组后需要再做条件过滤时,就无法使用where来做筛选,而having就是用来对分组后的结果做条件过滤的。查询语句中的各类关键字执行优先级为:from → where → select → group by → having → order by

子查询

子查询也可以理解成是查询嵌套,是指一种由多条SQL语句组成的查询语句,语法如下:

-- 基于一条SQL语句的查询结果进一步做查询
select*from(select*from表名where条件)as别名where条件;-- 将一条SQL语句的查询结果作为条件继续查询(只适用于子查询返回单值的情况)
select*from表名where字段名=(select字段名from表名where条件);-- 将一条SQL语句的查询结果作为条件继续查询(适用于子查询返回多值的情况)
select*from表名where字段名exists(select字段名from表名where条件);
-- 上述的exists可以换为not exists,表示查询不包含相应条件的数据-- 将一条SQL语句的多个查询结果,作为条件与多个字段进行范围查询
select*from表名where(字段1,字段2...)in(select字段1,字段2...from 表名);

在上述子查询语法中,exists的作用和in大致相同,只不过not in时会触发全表扫描,而not exists依旧可以走索引查询,因此通常情况下尽量使用not exists代替not in来查询数据。

关联查询

关联查询也被称之为连表查询,也就是指利用主外键连接多张表去查询数据,这几乎也是日常开发中写的最多的一类查询语句,MySQL中支持多种关联类型,如:

  • • 交叉连接

  • • 内连接

  • • 外连接:

    • • 左连接

    • • 右连接

    • • 全连接

语法如下:

-- 交叉连接:默认把前一张表的每一行数据与后一张表的所有数据做关联查询
select*from表1,表2...;-- 这种方式默认采用交叉连接的方式
select*from表1crossjoin表2;-- 显式声明采用交叉连接的方式-- 内连接:只返回两张表条件都匹配的数据
-- 隐式的内连接写法
select*from表1,表2...where表1.字段=表2.字段...;
-- 等值内连接
select*from表1别名1innerjoin表2别名2on别名1.字段=别名2.字段;
-- 不等式内连接
select*from表1别名1innerjoin表2别名2on别名1.字段<别名2.字段;-- 左外连接:左表为主,右表为次,无论左表在右表是否匹配,都返回左表数据,缺失的右表数据显示NULL
select*from表1leftjoin表2on表1.字段=表2.字段;-- 右外连接:和左连接相反,右表为主,左表为次,永远返回右表的所有数据
select*from表1rightjoin表2on表1.字段=表2.字段;-- 全外连接:两张表没有主次之分,每次查询都会返回两张表的所有数据,不匹配的显示NULL
-- MySQL中不支持全连接语法,只能通过union all语句,将左、右连接查询拼接起来实现
select*from表1leftjoin表2on表1.字段=表2.字段
unionall
select*from表1rightjoin表2on表1.字段=表2.字段;-- 继续拼接查询两张以上的表
select*from表1leftjoin表2on表1.字段=表2.字段leftjoin表3on表2.字段=表3.字段;
-- 通过隐式连接的方式,查询两张以上的表
select*from表1,表2,表3...where表1.字段=表2.字段and表1.字段=表3.字段...;
-- 通过子查询的方式,查询两张以上的表
select*from
(表1as别名1leftjoin表2as别名2on别名1.字段=别名2.字段)
leftjoin
表3as别名3on别名1.字段=别名3.字段;

多表联查时的笛卡尔积问题,所谓的笛卡尔积问题就是指两张表的所有数据都做关联查询,一般连表查询都需要指定连接的条件,但如果不指定时,MySQL默认会将左表每一条数据挨个和右表所有数据关联一次,然后查询一次数据。比如左表有3条数据,右表有4条数据,笛卡尔积情况出现时,一共就会查询出3 x 4 = 12条数据。

笛卡尔积现象出现时,会随着表数据增长越来越大,因此在连表查询时一定要消除笛卡尔积问题,咋消除呢?其实就是指定加上关联条件即可。

三、MySQL数据库函数

MySQL中提供了丰富的函数支持,包括数学函数、字符串函数、日期和时间函数、条件判断函数、系统信息函数、加密函数、格式化函数等,通过这些函数,一方面可以简化业务的代码量,另一方面还能更好的实现各类特殊业务需求,下来一起来聊聊MySQL支持的函数。

3.1、数学函数

数学函数是MySQL中最常用的一类函数,主要用来处理所有数值类型的字段值,一起来看看。

  • • abs(X):返回X的绝对值,如传进-1,则返回1

  • • ln(X):返回X的自然相对数。

  • • log(X,Y):返回以X的以Y为底的对数。

  • • log10(X):返回以X基数为10的对数。

  • • bin(X):返回X的二进制值。

  • • oct(X):返回X的八进制值。

  • • hex(X):返回X的十六进制值。

  • • mod(X,Y):返回X除以Y的余数。

  • • ceil(X) | ceiling(X):返回不小于X的最小整数,如传入1.23,则返回2

  • • round(X):返回X四舍五入的整数。

  • • floor(X):返回X向下取整后的值,如传入2.34,会返回2

  • • greatest(X1,X2....,Xn):返回集合中的最大整数值。

  • • least(X1,X2....,Xn):返回集合中的最小整数值。

  • • rand(N):返回一个0~N``0~1之间的随机小数(不传参默认返回0~1之间的随机小数)。

  • • sign(X):传入正数,返回1;传入负数,返回-1;传入0,返回0

  • • pow(X,Y) | power(X,Y):返回XY次方值。

  • • pi():返回四舍五入后的圆周率,3.141593

  • • sin(X):返回X的正弦值。

  • • asin(X):返回X的反正弦值。

  • • cos(X):返回X的余弦值。

  • • acos(X):返回X的反余弦值。

  • • tan(X):返回X的正切值。

  • • atan(X):返回X的反正切值。

  • • cot(X):返回X的余切值。

  • • radians(x):返回x由角度转化为弧度的值。

  • • degrees(x):返回x由弧度转化为角度的值。

  • • sqrt(X):返回X的平方根。

  • • exp(e,X):返回ex乘方的值。

  • • truncate(X,N):返回小数X保留N位精准度的小数。

  • • format(x,y):将x格式化位以逗号隔开的数字列表,y是结果的小数位数。

  • • inet_aton(ip):将IP地址以数字的形式展现。

  • • inet_ntoa(number):显示数字代表的IP地址。

  • • ......

3.2、字符串函数

  • • ascii(C):返回字符CASCII码。

  • • length(S):返回字符串的占位空间,传入“竹子爱熊猫”,返回15,一个汉字占位3字节。

  • • bit_length(S):返回字符串的比特长度。

  • • concat(S1,S2,...):合并传入的多个字符串。

  • • concat_wa(sep,S1,S2...):合并传入的多个字符串,每个字符串之间用sep间隔。

  • • position(str,s) | locate(str,s):返回sstr中第一次出现的位置,没有则返回0

  • • find_in_set(S,list):返回字符串Slist列表中的位置。

  • • insert(S1,start,end,S2):使用S2字符串替换掉S1字符串中start~end的内容。

  • • lcase(S) | lower(S):将传入的字符串中所有大写字母转换为小写。

  • • ucase(S) | upper(S):将传入的字符串中所有小写字母转换为大写。

  • • left(S,index):从左侧开始截取字符串Sindex个字符。

  • • right(S,index):从右侧开始截取字符串Sindex个字符。

  • • trim(S):删除字符S左右两侧的空格。

  • • rtrim(S):删除字符S右侧的空格。

  • • replace(S,old,new):使用new新字符替换掉S字符串中的old字符。

  • • repeat(str,count):将str字符串重复count次后返回。

  • • substring(S,index,N):截取S字符串,从index位置开始,返回长度为N的字符串。

  • • reverse(S):将传入的字符串反转,即传入Java,返回avaJ

  • • quote(str):用反斜杠转移str中的英文单引号。

  • • strcmp(S1,S2):比较两个字符是否相同。

  • • lpad(str,len,s):对str字符串左边填充lens字符。

  • • rpad(str,len,s):对str字符串右边填充lens字符。

3.3、日期和时间函数

  • • curdate() | current_date():返回当前系统的日期,如2022-10-21

  • • curtime() | current_time():返回当前系统的时间,如17:30:52

  • • now() | sysdate():返回当前系统的日期时间,如2022-10-21 17:30:59

  • • unix_timestamp():获取一个数值类型的unix时间戳,如1666348711

  • • from_unixtime():将unix_timestamp()获取的数值时间戳,格式化成日期格式。

  • • month(date):获取date中的月份。

  • • year(date):获取date中的年份。

  • • hour(date):获取date中的小时。

  • • minute(date):获取date中的分钟。

  • • second(date):获取date中的秒数。

  • • monthname(date):返回date中月份的英文名称。

  • • dayname(date):获取日期date是星期几,如Friday

  • • dayofweek(date):获取date位于一周的索引位置,周日是1、周一是2...周六是7

  • • week(date):获取date是本年的第多少周。

  • • quarter(date):获取date位于一年中的哪个季度(1~4)。

  • • dayofyear(date):获取date是本年的第多少天。

  • • dayofmonth(date):获取date是本月的第多少天。

  • • time_to_sec(time):将传入的时间time转换为秒数,比如"01:00:00" = 3600s

  • • date_add(date,interval 时间 单位) | adddate(...):将date与给定的时间按单位相加。

  • • date_sub(date,interval 时间 单位) | subdate(...):将date与给定的时间按单位相减。

  • • addtime(date,time):将date加上指定的时间,如addtime(now(),"01:01:01")

  • • subtime(date,time):将date减去指定的时间。

  • • datediff(date1,date2):计算两个日期之间的间隔天数。

  • • last_day(date):获取date日期这个月的最后一天。

  • • date_format(date,format):将一个日期格式化成指定格式,format可选项如下:

    • • %a:工作日的英文缩写(Sun~Sat)。

    • • %b:月份的英文缩写(Jan~Dec)。

    • • %c:月份的数字格式(1~12)。

    • • %M:月份的英文全称(January~December)。

    • • %D:带有英文后缀的数字月份(1th、2st、3nd....)。

    • • %d:一个月内的天数,双数形式(01、02、03....31)。

    • • %e:一个月内的天数,单数形式(1、2、3、4....31)。

    • • %f:微妙(000000~999999)。

    • • %H:一天内的小时,24小时的周期(00、01、02...23)。

    • • %h | %I:一天内的小时,12小时的周期(01、02、03...12)。

    • • %i:一小时内的分钟(00~59)。

    • • %j:一年中的天数(001~366)。

    • • %k:以24小时制显示时间(00~23)。

    • • %l:以12小时制显示时间(01~12)。

    • • %m:月份的数字形式,双数形式(01~12)。

    • • %p:一天内的时间段(上午AM、下午PM)。

    • • %r12小时制的时间(12:01:09 AM)。

    • • %S | %s:秒数,双数形式(00~59)。

    • • %T24小时制的时间(23:18:22)。

    • • %U:一年内的周(00~53)。

  • • time_format(time,format):将一个时间格式化成指定格式。

  • • str_to_date(str,format):将日期字符串,格式化成指定格式。

  • • timestampdiff(unit,start,end):计算两个日期之间间隔的具体时间,unit是单位:

    • • year:年。

    • • quarter:季度。

    • • month:月。

    • • week:周。

    • • day:天。

    • • hour:小时。

    • • minute:分钟。

    • • second:秒数。

    • • microsecond:微妙。

  • • weekday(date):返回date位于一周内的索引(0是周一...6是周日)。

3.4、聚合函数

聚合函数一般是会结合select、group by having筛选数据使用。

  • • max(字段名):查询指定字段值中的最大值。

  • • min(字段名):查询指定字段值中的最小值。

  • • count(字段名):统计查询结果中的行数。

  • • sum(字段名):求和指定字段的所有值。

  • • avg(字段名):对指定字段的所有值,求出平均值。

  • • group_concat(字段名):返回指定字段所有值组合成的结果,如下:

  • • distinct(字段名):对于查询结果中的指定的字段去重。

这里稍微介绍一个日常业务中碰到次数较多的需求:

select *from zz_users;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time       |
+---------+-----------+----------+----------+---------------------+
|1|熊猫|女|6666|2022-08-1415:22:01|
|2|竹子|男|1234|2022-09-1416:17:44|
|3|子竹|男|4321|2022-09-1607:42:21|
|4|黑熊|男|8888|2022-09-1723:48:29|
|8|猫熊|女|8888|2022-09-2717:22:29|
|9|棕熊|男|0369|2022-10-1723:48:29|
+---------+-----------+----------+----------+---------------------+-- 基于性别字段分组,然后显示各组中的所有ID
select
convert(group_concat(user_id orderby user_id asc separator ",")
using utf8)as"分组统计"
from`zz_users`groupby user_sex;
+-------------+
|分组统计|
+-------------+
|1,8|
|2,3,4,9|
+-------------+

上述利用了group_concat()、group by实现了按照一个字段分组后,显示对应分组的所有ID

3.5、控制流程函数

  • • if(expr,r1,r2)expr是表达式,如果成立返回r1,否则返回r2

  • • ifnull(v,r):如果v不为null则返回v,否则返回r

  • • nullif(v1,v2):如果v1 == v2,则返回null,如果不相等则返回V1

-- if的用例
selectif(user_id >3,"√","×")from zz_users;-- ifnull的用例
select ifnull(user_id,"×")from zz_users;-- case语法1:
case<表达式>
when<值1>then<操作>
when<值2>then<操作>
...
else<操作>
end;
-- 用例:判断当前时间是星期几
selectcase weekday(now())
when0then'星期一'
when1then'星期二'
when2then'星期三'
when3then'星期四'
when4then'星期五'
when5then'星期六'
else'星期天'
endas"今天是星期几?";-- case语法2:
case
when<条件1>then<命令>
when<条件2>then<命令>
...
else commands
end;
-- 用例:判断今天是星期几
selectcase
when weekday(now())=0then'星期一'
when weekday(now())=1then'星期二'
when weekday(now())=2then'星期三'
when weekday(now())=3then'星期四'
when weekday(now())=4then'星期五'
when weekday(now())=5then'星期六'
else'星期天'
endas"今天是星期几?";

简单聊一下CASE语法,第一种语法就类似于Java中的switch,而第二种语法就类似于多重if,通过CASE语法能够让SQL更加灵活,完成类似于存储过程的工作。

3.6、加密函数

  • • password(str):将str字符串以数据库密码的形式加密,一般用在设置DB用户密码上。

  • • md5(str):对str字符串以MD5不可逆算法模式加密。

  • • encode(str,key):通过key密钥对str字符串进行加密(对称加密算法)。

  • • decode(str,key):通过key密钥对str字符串进行解密。

  • • aes_encrypt(str,key):通过key密钥对str字符串,以AES算法进行加密。

  • • aes_decrypt(str,key):通过key密钥对str字符串,以AES算法进行解密。

  • • sha(str):计算str字符串的散列算法校验值。

  • • encrypt(str,salt):使用salt盐值对str字符串进行加密。

  • • decrypt(str,salt):使用salt盐值对str字符串进行解密。

3.7、系统函数

  • • version():查询当前数据库的版本。

  • • connection_id():返回当前数据库连接的ID

  • • database() | schema():返回当前连接位于哪个数据库,即use进入的库。

  • • user():查询当前的登录的所有用户信息。

  • • system_user():返回当前登录的所有系统用户信息。

  • • session_user():查询所有连接的用户信息。

  • • current_user():查询当前连接的用户信息。

  • • charset(str):返回当前数据库的编码格式。

  • • collation(str):返回当前数据库的字符排序规则。

  • • benchmark(count,expr):将expr表达式重复运行count次。

  • • found_rows():返回最后一个select查询语句检索的数据总行数。

  • • cast(v as 类型):将v转换为指定的数据类型。

就算某个功能在MySQL中没有提供函数支持,也可以通过create function的方式自定义存储函数。

四、MySQL支持的数据类型

这里所谓的数据类型,也就是只在创建表时可以选择的列字段类型,在MySQL中其实可以通过:

  • • help data types;:查看当前版本支持的所有数据类型。如下(MySQL5.1版本):

图片

数据类型

总体可分为数值类型、字符/串类型、时间/日期类型、其他类型四种,下面一起来聊聊吧。

4.1、数值类型

  • • tinyint:小整数类型,占位1Bytes,取值范围-128~127

  • • smallint:中整数类型,占位2Bytes,取值范围-32768~32767

  • • mediumint:中大整数类型,占位3Bytes,取值范围-8388608~8388607

  • • int | integer:常用整数类型,占位4Bytes,取值范围-2147483548~2147483647

  • • bigint:超大整数类型,占位8Bytes,取值范围-9223372036854775808~9223372036854775807

  • • float:单精度浮点数类型,占位4Bytes,取值范围-3.4E+38 ~ 3.4E+38

  • • double:双精度浮点数类型,占位8Bytes,取值范围-1.7E-308~1.7E+308

  • • decimal(m,d):小数类型,占位和取值范围都依赖m、d值决定,m是小数点后面的精度,d是小数点前面的标度。

  • • bit(m):存储位值,可存储m个比特位,取值范围是1~64

4.2、字符串类型

  • • char:定长字符串类型,存储空间0~255Bytes

  • • varchar:变长字符串类型,存储空间0~65535Bytes

  • • tinyblob:二进制短字符串类型,存储空间0~255Bytes

  • • tinytext:短文本字符串类型,存储空间0~255Bytes

  • • blob:二进制长字符串类型,存储空间0~65535Bytes

  • • text:长文本字符串类型,存储空间0~65535Bytes

  • • mediumblob:二进制大字符串类型,存储空间0~16777215Bytes

  • • mediumtext:大文本字符串类型,存储空间0~16777215Bytes

  • • longblob:二进制超大字符串类型,存储空间0~4294967295Bytes

  • • longtext:超大文本字符串类型,存储空间0~4294967295Bytes

  • • binary(m):定长字符串类型,存储空间为M个字符。

  • • varbinary(m):定长字符串类型,存储空间为M个字符+1个字节。

一般在为列指定数据类型时,都会varchar(255)这样写,其实中间的这个数字限制的并不是字节长度,而是字符数量,比如varchar(255),表示该列最大能存储255个字符。

4.3、时间/日期类型

  • • date:日期类型,占位3Bytes,格式为YYYY-MM-DD

  • • time:时间类型,占位3Bytes,格式为hh:mm:ss

  • • year:年份类型,占位1Bytes,格式为YYYY

  • • datetime:日期时间类型,占位8Bytes,格式为YYYY-MM-DD hh:mm:ss

  • • timestamp:时间戳类型,占位4Bytes,格式为YYYYMMDDhhmmss,最大可精确到微妙。

4.4、其他类型

  • • jsonMySQL5.7版本引入的,在此之前只能用字符串类型来存储json数据,需要通过函数辅助使用:

    • • json_array(...):存储一个json数组的数据。

    • • json_array_insert(字段,'$[下标]',"值"):在指定的json数组下标位置上插入数据。

    • • json_object(...):存储一个json对象。

    • • json_extract(字段,'$.键'):查询键为某个值的所有数据。

    • • json_search(....):通过值查询键。

    • • json_keys(字段):获取某个字段的所有json键。

    • • json_set(字段,'$.键',"值"):更新某个键的json数据。

    • • json_replace(...):替换某个json中的数据。

    • • json_remove(字段,'$.键'):删除某个json数据。

    • 还有一些其他json类型的函数,这里不再说明,一般json类型用的较少。

  • • enum(选项1,选项2...选项n):新增数据时只能从已有的选项中选择一个并插入。

  • • set(选项1,选项2...选项n):新增数据时可以从已有的选项中选择多个并插入。

  • • eometry、point、linestring、polygon:空间类型(接触不多)。

稍微解释一下enum、set类型,这两种类型就类似于平时的单选框和多选框,必须从已有的选项中选择,两者的区别在于:enum枚举类型只能选择一个选项,而set集合类型可以选择多个选项(其实用的比较少,多数情况下都是直接在客户端中处理)。

五、索引相关的命令

-- 创建一个普通索引(方式①)
create index 索引名ON表名(列名(索引键长度)[ASC|DESC]);
-- 创建一个普通索引(方式②)
altertable表名add index 索引名(列名(索引键长度)[ASC|DESC]);
-- 创建一个普通索引(方式③)
CREATETABLE tableName(columnName1 INT(8)NOTNULL,columnName2 ....,
.....,index [索引名称](列名(长度))
);
-- 后续其他类型的索引都可以通过这三种方式创建-- 创建一个唯一索引
createunique索引名ON表名(列名(索引键长度)[ASC|DESC]);-- 创建一个主键索引
altertable表名addprimary key 索引名(列名);-- 创建一个全文索引
create fulltext index 索引名ON表名(列名);-- 创建一个前缀索引
create index 索引名ON表名(列名(索引键长度));-- 创建一个空间索引
altertable表名add spatial key 索引名(列名);-- 创建一个联合索引
create index 索引名ON表名(列名1(索引键长度),列名2,...列名n);

上面将MySQL中创建各类索引的多种方式都列出来了,接着再聊聊索引查看、使用与管理的命令。

-- 查看一张表上的所有索引
show index from表名;-- 删除一张表上的某个索引
drop index 索引名on表名;-- 强制指定一条SQL走某个索引查找数据
select*from表名 force index(索引名)where.....;-- 使用全文索引(自然搜索模式)
select*from表名wherematch(索引列) against('关键字');
-- 使用全文索引(布尔搜索模式)
select*from表名wherematch(索引列) against('布尔表达式'inboolean mode);
-- 使用全文索引(拓展搜索模式)
select*from表名wherematch(索引列) against('关键字'with query expansion);-- 分析一条SQL是否命中了索引
explain select*from表名where 条件....;

六、事务与锁相关的命令

  • • start transaction; | begin; | begin work;:开启一个事务。

  • • commit;:提交一个事务。

  • • rollback;:回滚一个事务。

  • • savepoint 事务点名称;:添加一个事务点。

  • • rollback to 事务点名称;:回滚到指定名称的事务点。

  • • release savepoint 事务点名称;:删除一个事务点。

  • • select @@tx_isolation;:查询事务隔离级别(方式一)。

  • • show variables like '%tx_isolation%';:查询事务隔离级别(方式二)。

  • • set transaction isolation level 级别:设置当前连接的事务隔离级别。

  • • set @@tx_isolation = "隔离级别";:设置当前会话的事务隔离级别。

  • • set global transaction isolation level 级别;:设置全局的事务隔离级别,选项如下:

    • • read uncommitted:读未提交级别。

    • • read committed:读已提交级别。

    • • repeatable-read:可重复读级别。

    • • serializable:序列化级别。

  • • show variables like 'autocommit';:查看自动提交事务机制是否开启。

  • • set @@autocommit = 0|1|ON|OFF;:开启或关闭事务的自动提交。

  • • select ... lock in share mode;:手动获取共享锁执行SQL语句。

  • • select ... for share;MySQL8.0之后优化版的共享锁写法。

  • • select ... for update;:手动获取排他锁执行。

  • • lock tables 表名 read;:获取表级别的共享锁。

  • • lock tables 表名 write;:获取表级别的排他锁。

  • • show open tables where in_use > 0;:查看目前数据库中正在使用的表锁。

  • • flush tables with read lock;:获取全局锁。

  • • unlock tables;:释放已获取的表锁/全局锁。

  • • update 表名 set version=version+1 ... where... and version=version;:乐观锁模式执行。

七、存储过程、存储函数与触发器

-- 创建一个存储过程
DELIMITER $
CREATE
PROCEDURE存储过程名称(返回类型参数名1参数类型1,....)
[......]
BEGIN
-- 具体组成存储过程的SQL语句....
END $
DELIMITER ;-- 创建一个存储函数
DELIMITER $
CREATE
FUNCTION存储函数名称(参数名1参数类型1,....)
RETURNS数据类型
[NOT]DETERMINISTIC statements
BEGIN
-- 具体组成存储函数的SQL语句....
END $
DELIMITER ;-- 创建一个触发器
CREATETRIGGER触发器名称
{BEFORE | AFTER}{INSERT|UPDATE|DELETE}ON表名
FOREACHROW
-- 触发器的逻辑(代码块);-- ------------- 用户变量与局部变量 ---------------
-- 定义、修改用户变量
set@变量名称=变量值;
-- 查询用户变量
select@变量名称;-- 定义局部变量
DECLARE变量名称数据类型default默认值;
-- 为局部变量赋值(方式1)
SET变量名=变量值;
-- 为局部变量赋值(方式2)
SET变量名:=变量值;
-- 为局部变量赋值(方式3)
select查询结果字段into变量名from表名;-- ------------- 流程控制 ---------------
-- if、elseif、else条件分支语法
IF 条件判断THEN
-- 分支操作.....
ELSEIF 条件判断 THWN
-- 分支操作.....
ELSE
-- 分支操作.....
END IF-- case分支判断语句
-- 第一种语法
CASE变量
WHEN值1THEN
-- 分支操作1....
WHEN值2THEN
-- 分支操作2....
.....
ELSE
-- 分支操作n....
ENDCASE;-- 第二种语法
CASE
WHEN条件判断1THEN
-- 分支操作1....
WHEN条件判断2THEN
-- 分支操作2....
.....
ELSE
-- 分支操作n....
ENDCASE;-- 循环:LOOP、WHILE、REPEAT
-- loop循环
循环名称:LOOP
-- 循环体....
END LOOP 循环名称;-- while循环
【循环名称】:WHILE 循环条件 DO
-- 循环体....
END WHILE 【循环名称】;-- repeat循环
【循环名称】:REPEAT
-- 循环体....
UNTIL 结束循环的条件判断
END REPEAT 【循环名称】;-- 循环跳转
LEAVE 【循环名称】;-- 结束某个循环体
ITERATE 【循环名称】;-- 跳出某个循环体,继续下次循环-- ------------- 存储过程的游标 ---------------
-- ①声明(创建)游标
DECLARE游标名称CURSORFORselect...;-- ②打开游标
OPEN游标名称;-- ③使用游标
FETCH游标名称INTO变量名称;-- ④关闭游标
CLOSE 游标名称;

在上面列出了MySQL中存储过程、存储函数与触发器的相关语法,接着再来聊聊管理的命令:

  • • SHOW PROCEDURE STATUS;:查看当前数据库中的所有存储过程。

  • • SHOW PROCEDURE STATUS WHERE db = '库名' AND NAME = '过程名';:查看指定库中的某个存储过程。

  • • SHOW CREATE PROCEDURE 存储过程名;:查看某个存储过程的源码。

  • • ALTER PROCEDURE 存储过程名称 ....:修改某个存储过程的特性。

  • • DROP PROCEDURE 存储过程名;:删除某个存储过程。

  • • SHOW FUNCTION STATUS;:查看当前数据库中的所有存储函数。

  • • SHOW CREATE FUNCTION 存储过程名;:查看某个存储函数的源码。

  • • ALTER FUNCTION 存储过程名称 ....:修改某个存储函数的特性。

  • • DROP FUNCTION 存储过程名;:删除某个存储函数。

  • • SHOW TRIGGERS;:查看当前数据库中定义的所有触发器。

  • • SHOW CREATE TRIGGER 触发器名称;:查看当前库中指定名称的触发器。

  • • SELECT * FROM information_schema.TRIGGERS;:查看MySQL所有已定义的触发器。

  • • DROP TRIGGER IF EXISTS 触发器名称;:删除某个指定的触发器。

当然,如若你对这块感兴趣,详细的教程可参考上篇:《MySQL存储过程与触发器》。

八、MySQL用户与权限管理

  • • create user 用户名@'IP' identified by 密码;:创建一个新用户。

  • • drop user 用户名@'IP';:删除某个用户。

  • • set password = password(新密码);:为当前用户设置新密码。

  • • set password for 用户名 = password(新密码);:为指定用户设置新密码(需要权限)。

  • • alter user 用户名@'IP' identified by 新密码;:使用root账号修改密码。

  • • mysqladmin -u用户名 -p旧密码 password 新密码;:使用mysqladmin工具更改用户密码。

  • • rename user 原用户名 to 新用户名;:对某个用户重命名。

  • • show grants;:查看当前用户拥有的权限。

  • • show grants for 用户名;:查看指定用户拥有的权限。

  • • grant 权限1,权限2... on 库名.表名 to 用户名;:为指定用户授予权限。

    • • *.*:全局权限,表示该用户可对所有库、所有表进行增删改查操作。

    • • 库名.*:单库权限,表示该用户可对指定库下的所有表进行增删改查操作。

    • • 库名.表名:单表权限,表示该用户可对指定表进行增删改查操作。

    • • insert:插入表数据的权限。

    • • delete:删除表数据的权限。

    • • update:修改表数据的权限。

    • • select:查询表数据的权限。

    • • alter:修改表结构的alter权限。

    • • alter routine:修改子程序(存储过程、函数、触发器)的alter权限。

    • • create:创建表的create权限。

    • • create routine:创建存储过程、存储函数、触发器的权限。

    • • create temporary tables:创建临时表的权限。

    • • create user:创建/删除/重命名/授权用户的权限。

    • • create view:创建视图的权限。

    • • drop:删除表的权限。

    • • execute:执行存储过程的权限。

    • • file:导出、导入表数据的权限。

    • • index:创建和删除索引的权限。

    • • lock tables:获取表锁的权限。

    • • process:查询工作线程的权限。

    • • references:这个在MySQL中没有。

    • • reload:请空表的权限。

    • • replication clinet:获取主节点、从节点地址的权限。

    • • replication slave:复制主节点数据的权限。

    • • show databases:查看所有数据库的权限。

    • • show view:查看所有视图的权限。

    • • shutdown:关闭数据库服务的权限。

    • • super:修改主节点信息的权限。

    • • all privileges:所有权限。

    • • 权限可选项:

    • • usage:不授予这些权限。其他权限全部授予。

    • • grant option:授予这些权限,其他权限全部不授予。

    • • 权限范围可选项:

  • • revoke 权限1,权限2... on 库名.表名 from 用户名;:撤销指定用户的指定权限。

  • • revoke all privileges from 用户名 with grant option;:撤销一个用户的所有权限。

  • • flush privileges;:刷新权限。

  • • select user,password,host from mysql.user;:查询当前库中的所有用户信息。

  • • MySQL8.0版本后推出的密码管理机制:

    • • set persist default_password_lifetime=90;:设置所有用户的密码在90天后失效。

    • • create user 用户@IP password expire interval 90 day;:创建用户时设置失效时间。

    • • alter user 用户名@IP password expire interval 90 day;:设置指定用户密码失效。

    • • alter user 用户名@IP password expire never;:设置指定用户的密码永不失效。

    • • alter user 用户名@IP password expire default;:使用默认的密码失效策略。上述给出了一系列的用户管理和权限管理的命令,最后稍微提一下创建用户时的注意事项:```sql

-- 创建一个名为 zhuzi 的用户 create user 'zhuzi'@'196.xxx.xxx.xxx' identified by "123456";

在创建用户时需要在用户名称后面跟一个`IP`地址,这个`IP`的作用是用来限制登录用户的机器,如果指定为具体`IP`,则表示只能由该`IP`的机器登录该用户,如果写`%`表示任意设备都能使用该用户名登录连接。
>同时也最后提一嘴,`MySQL`对于所有的用户信息,都会放在自带的`mysql`库的`user`表中存储,因此也可以对表执行`insert、delete、update、select`操作,来实现管理用户的功能。## 九、MySQL视图与临时表
-`create view 视图名 as select ...;`:对查询出的结果集建立一个指定名称的视图。
-`select * from 视图名;`:基于某个已经创建的视图查询数据。
-`show create view 视图名;`:查看某个已存在的视图其详细信息。
-`desc 视图名;`:查看某个视图的字段结构。
-`alter view 视图名(字段1,...) as select 字段1...;`:修改某个视图的字段为查询字段。
-`drop view 视图名;`:删除某个视图。
-`create temporary table 表名(....);`:创建一张临时表(方式1)。
-`create temporary view 表名 as select ...;`:创建一张临时表(方式2)。
-`truncate table 临时表名;`:清空某张临时表的数据。`MySQL`的临时表本质上是一种特殊的视图,被称为不可更新的视图,也就是临时表只支持查询数据,不支持增删改操作,因此也可以通过创建视图的方式创建临时表,在创建语句中加入`temporary`关键字即可,不指定默认为`undedined`,意思是自动选择视图结构,一般为`merge`结构,表示创建一个支持增删改查的视图。## 十、数据的导出、导入与备份、还原
数据库的备份其实本质上就是指通过导出数据的形式,或者拷贝表文件的方式来制作数据的副本,数据恢复/还原即是指在数据库故障、异常、错误的情况下,通过导入原本的数据副本,将数据恢复到正常状态,下面来介绍`MySQL`中提供的相关命令。
```sql
-- --------使用 mysqldump 工具做数据的逻辑备份(导出的是sql语句)-----------
-- 导出MySQL中全部的库数据(使用--all-databases 或者 -A 参数)
mysqldump -uroot -p密码 --all-databases > 备份文件名.sql-- 导出MySQL中一部分的库数据(使用--databases 或者 -B 参数)
mysqldump -uroot -p密码 --databases > 备份文件名.sql-- 导出MySQL单库中的一部分表数据
mysqldump –u 用户名 –h主机名 –p密码 库名[表名1,表名2...]> 备份文件名.sql-- 导出MySQL单表的部分数据(使用 --where 参数)
mysqldump -u用户名 -p 库名 表名 --where="条件" > 备份文件名.sql-- 排除某些表,导出库中其他的所有数据(使用 --ignore-table 参数)
mysqldump -u用户名 -p 库名 --ignore-table=表名1,表名2... > 备份文件名.sql-- 只导出表的结构(使用 --no-data 或者 -d 选项)
mysqldump -u用户名 -p 库名 --no-data > 备份文件名.sql-- 只导出表的数据(使用 --no-create-info 或者 -t 选项)
mysqldump -u用户名 -p 库名 --no-create-info > 备份文件名.sql-- 导出包含存储过程、函数的库数据(使用--routines 或者 -R选项)
mysqldump -u用户名 -p -R --databases 库名 > 备份文件名.sql-- 导出包含事件(触发器)的库数据(使用 --events 或者 -E选项)
mysqldump -u用户名 -p -E --databases 库名 > 备份文件名.sql-- --------使用 mysql 工具来恢复备份的数据(导入xx.sql文件执行)-----------
-- 恢复库级别的数据(包含了建库语句的情况下使用)
mysql -u用户名 -p < xxx.sql-- 恢复库中表级别的数据
mysql -u用户名 -p 库名 < xxx.sql-- ----------以物理形式备份数据(导出的是表数据) ------------
-- 查看数据库导出数据的路径(如果没有则需在`my.ini/my.conf`中配置)
show variables like '%secure_file_priv%';-- 导出一张表的数据为txt文件(使用 select ... into outfile 语句)
select * from 表名 into outfile "备份文件名.txt";-- 导出一张表的数据为txt文件(使用 mysql 工具)
mysql -u用户名 -p --execute="select ...;" 库名 > "数据存放目录/xxx.txt"-- 导出一张表的结构和数据为sql、txt文件(使用 mysqldump -T 的方式)
mysqldump -u用户名 -p -T "数据存放目录" 库名 文件名-- 导出一张表的数据为txt文件,以竖排形式存储(使用 mysql –veritcal 的方式)
mysql -u用户名 -p -veritcal --execute="select ...;" 库名 > "数据存放目录/xxx.txt"-- 导出一张表的数据为xml文件(使用 mysql -xml 的方式)
mysql -u用户名 -p -xml --execute="select ...;" 库名 > "数据存放目录/xxx.xml"-- -----------通过物理数据文件恢复数据----------------
-- 使用load data infile 的方式导入.txt 物理数据
load data infile "数据目录/xxx.txt" into table 库名.表名;-- 使用 mysqlimport 工具导入xxx.txt物理数据
mysqlimport -u用户名 -p 库名 '数据存放目录/xxx.txt' --fields-terminatedby=',' --fields-optionally-enclosed-by='\"'-- 使用 mysqldump 工具迁移数据
mysqldump –h 地址1 –u用户名 –p密码 –-all-databases | mysql –h地址2 –u用户名 –p密码

上述列出了一系列数据导出导入、备份恢复、迁移等命令,这些都是MySQL自身就支持的方式,但这些自带的命令或工具,在一些情况下往往没有那么灵活、方便,因此在实际情况下,可以适当结合第三方工具来完成,比如:

  • • 较大的数据需要做物理备份时,可以通过xtrabackup备份工具来完成。

  • • MySQL5.5版本之前的MyISAM表,可以通过MySQLhotcopy工具做逻辑备份(速度最快)。

  • • 不同版本的MySQL可以使用XtraBackup备份工具来做数据迁移。

  • • MySQL、Oracle之间可以通过MySQL Migration Toolkit工具来做数据迁移。

  • • MySQL、SQL Server之间可以通过MyODBC工具来做数据迁移。

十一、表分区相关的命令

-- 创建范围分区
createtable`表名`(
`xxx` xxx notnull,
....
)
partitionbyrange(xxx)(
partition分区名1values less than (范围) data directory ="/xxx/xxx/xxx",
partition分区名2values less than (范围) data directory ="/xxx/xxx/xxx",
......
);-- 创建枚举分区
createtable`表名`(
`xxx` xxx notnull,
....
)
partitionby list(xxx)(
partition分区名1valuesin(枚举值1,枚举值2...),
partition分区名2valuesin(枚举值),
......
);-- 创建常规哈希分区
createtable`表名`(
`xxx` xxx notnull,
....
)
partitionby hash(xxx)
partitions 分区数量;-- 创建线性哈希分区
createtable`表名`(
`xxx` xxx notnull,
....
)
partitionby linear hash(xxx)
partitions 分区数量;-- 创建Key键分区
createtable`表名`(
`xxx` xxx notnull,
....
)
partitionby key(xxx)
partitions 分区数量;-- 创建Sub子分区
createtable`表名`(
`xxx` xxx notnull,
....
)
partitionbyrange(父分区键)
subpartition by hash(子分区键)(
partition分区名1values less than (范围1)(subpartition 子分区名1,subpartition 子分区名2,
......
),
partition分区名2values less than (范围2)(subpartition 子分区名1,subpartition 子分区名2,
......
),
......
);-- 查询一张表各个分区的数据量
selectpartition_name as"分区名称",table_rows as"数据行数"
frominformation_schema.partitions 
wheretable_name ='表名';-- 查询一张表父子分区的数据量
selectpartition_name as"父分区名称",subpartition_name as"子分区名称",table_rows as"子分区行数"
frominformation_schema.partitions 
wheretable_name ='表名';-- 查询MySQL中所有表分区的信息
select*from information_schema.partitions;-- 查询一张表某个分区中的所有数据
select*from表名partition(分区名);-- 对于一张已存在的表添加分区
altertable表名 reorganize partition分区名into(
partition分区名1values less than (范围) data directory ="/xxx/xxx/xxx",
partition分区名2values less than (范围) data directory ="/xxx/xxx/xxx",
......
);-- 将多个分区合并成一个分区
altertable表明 reorganize partition分区名1,分区名2...into(
partition新分区名values less than (范围)
);-- 清空一个分区中的所有数据
altertable表名truncatepartition分区名;-- 删除一个表的指定分区
altertable表名droppartition分区名;-- 重建一张表的分区
altertable表名 rebuild partition分区名;-- 分析一个表分区
altertable表名 analyze partition分区名;
-- 优化一个表分区
altertable表名 optimize partition分区名;
-- 检查一个表分区
altertable表名checkpartition分区名;
-- 修复一个表分区
altertable表名 repair partition分区名;-- 减少hash、key分区方式的 n 个分区
altertable表名 coalesce partition n;-- 将一张表的分区切换到另一张表
altertable表名1 exchange partition分区名withtable表名2;-- 移除一张表的所有分区
altertable表名 remove partitioning;

十二、MySQL、InnoDB、MyISAM的参数

``参数,也被称之为MySQL的系统变量,这些变量是影响MySQL运行的关键,对每个参数做出不同调整,都有可能直接影响到线上数据库的性能。

图片

MySQL系统变量

通过xpath的方式提取数据,大概能够得知MySQL系统变量大概一千个上下。

但是要注意,虽说MySQL中有一千多个对外暴露的系统参数,但并不是所有的参数都可以让用户调整,MySQL的系统参数分为了三类:
一类是由MySQL自己维护的参数,这类参数用户无法调整。
第二类是以配置文件的形式加载的参数,这类参数必须在MySQL停机的情况下才能更改。
第三类是运行时的系统参数,这类是可以由用户去做动态调整的。

咱们需要关心的重点就是第三类参数,那如何观察这类参数呢?方式如下:

  • • show global variables;:查看全局所有用户级别可以看到的系统变量。

  • • show session variables; | show variables;:查看当前会话的所有系统变量。

  • • show variables like '%关键字%';:使用模糊查询搜索某个系统变量。

MySQL5.1MySQL8.0版本的执行结果如下:

图片

可调整的系统变量

可以很明显的从结果中得知:MySQL5.1版本中存在278个系统变量,MySQL8.0版本中存在557个系统变量,这仅仅只是社区版,而在商业版的MySQL中,其系统参数会更多,下面调出一些重点来聊一聊。

  • • max_connectionsMySQL的最大连接数,超出后新到来的连接会阻塞或被拒绝。

  • • version:当前数据库的版本。

  • • ft_min_word_len:使用MyISAM引擎的表中,全文索引最小搜索长度。

  • • ft_max_word_len:使用MyISAM引擎的表中,全文索引最大搜索长度。

  • • ft_query_expansion_limitMyISAM中使用with query expansion搜索的最大匹配数。

  • • innodb_ft_min_token_sizeInnoDB引擎的表中,全文索引最小搜索长度。

  • • innodb_ft_max_token_sizeInnoDB引擎的表中,全文索引最大搜索长度。

  • • optimizer_switchMySQL隐藏参数的开关。

  • • skip_scan:是否开启索引跳跃扫描机制。

  • • innodb_page_sizeInnoDB引擎数据页的大小。

  • • tx_isolation:事务的隔离级别。

  • • autocommit:事务自动提交机制。

  • • innodb_autoinc_lock_mode:插入意向锁的工作模式。

  • • innodb_lock_wait_timeoutInnoDB锁冲突时,阻塞的超时时间。

  • • innodb_deadlock_detect:是否开启InnoDB死锁检测机制。

  • • innodb_max_undo_log_size:本地磁盘文件中,Undo-log的最大值,默认1GB

  • • innodb_rollback_segments:指定回滚段的数量,默认为1个。

  • • innodb_undo_directory:指定Undo-log的存放目录,默认放在.ibdata文件中。

  • • innodb_undo_logs:指定回滚段的数量,默认为128个,也就是之前的innodb_rollback_segments

  • • innodb_undo_tablespaces:指定Undo-log分成几个文件来存储,必须开启innodb_undo_directory参数。

  • • back_log:回滚日志的最大回撤长度(一条数据的最长版本链长度)。

  • • innodb_undo_log_truncate:是否开启Undo-log的压缩功能,即日志文件超过一半时自动压缩,默认关闭。

  • • innodb_flush_log_at_trx_commit:设置redo_log_buffer的刷盘策略,默认每次提交事务都刷盘。

  • • innodb_log_group_home_dir:指定redo-log日志文件的保存路径,默认为./

  • • innodb_log_buffer_size:指定redo_log_buffer缓冲区的大小,默认为16MB

  • • innodb_log_files_in_group:指定redo日志的磁盘文件个数,默认为2个。

  • • innodb_log_file_size:指定redo日志的每个磁盘文件的大小限制,默认为48MB

  • • innodb_log_write_ahead_size:设置checkpoint刷盘机制每次落盘动作的大小。

  • • innodb_log_compressed_pages:是否对Redo日志开启页压缩机制,默认ON

  • • innodb_log_checksumsRedo日志完整性效验机制,默认开启。

  • • log_bin:是否开启bin-log日志,默认ON开启,表示会记录变更DB的操作。

  • • log_bin_basename:设置bin-log日志的存储目录和文件名前缀,默认为./bin.0000x

  • • log_bin_index:设置bin-log索引文件的存储位置,因为本地有多个日志文件,需要用索引来确定目前该操作的日志文件。

  • • binlog_format:指定bin-log日志记录的存储方式,可选Statment、Row、Mixed

  • • max_binlog_size:设置bin-log本地单个文件的最大限制,最多只能调整到1GB

  • • binlog_cache_size:设置为每条线程的工作内存,分配多大的bin-log缓冲区。

  • • sync_binlog:控制bin-log日志的刷盘频率。

  • • binlog_do_db:设置后,只会收集指定库的bin-log日志,默认所有库都会记录。

  • • log-errorerror-log错误日志的保存路径和名字。

  • • slow_query_log:设置是否开启慢查询日志,默认OFF关闭。

  • • slow_query_log_file:指定慢查询日志的存储目录及文件名。

  • • general_log:是否开启查询日志,默认OFF关闭。

  • • general_log_file:指定查询日志的存储路径和文件名。

  • • innodb_buffer_pool_sizeInnoDB缓冲区的大小。

  • • innodb_adaptive_hash_index:是否开启InnoDB的自适应哈希索引机制。

  • • innodb_compression_level:调整压缩的级别,可控范围在1~9,越高压缩效果越好,但压缩速度也越慢。

  • • innodb_compression_failure_threshold_pct:当压缩失败的数据页超出该比例时,会加入数据填充来减小失败率,为0表示禁止填充。

  • • innodb_compression_pad_pct_max:一个数据页中最大允许填充多少比例的空白数据。

  • • innodb_log_compressed_pages:控制是否对redo-log日志的数据也开启压缩机制。

  • • innodb_cmp_per_index_enabled:是否对索引文件开启压缩机制。

  • • character_set_client:客户端的字符编码格式。

  • • character_set_connection:数据库连接的字符编码格式。

  • • character_set_database:数据库的字符编码格式。

  • • character_set_results:返回的结果集的编码格式。

  • • character_set_serverMySQL-Server的字符编码格式。

  • • character_set_system:系统的字符编码格式。

  • • collation_database:数据库的字符排序规则。

十三、MySQL常见的错误码

MySQL的错误信息由ErrorCode、SQLState、ErrorInfo三部分组成,即错误码、SQL状态、错误信息三部分组成,如下:

ERROR 1045 (28000): Access denied for user 'zhuzi'@'localhost' (using password: YES)

其中1045属于错误状态码,28000属于SQL状态,后面跟着的则是具体的错误信息,不过MySQL内部大致定义了两三千个错误码,其错误码的定义位于include/mysqld_error.h、include/mysqld_ername.h文件中,而SQLState的定义则位于include/sql_state.h文件中,所有的错误信息列表则位于share/errmsg.txt文件中,因此大家感兴趣的可自行编译MySQL源码查看。

相关文章:

记录些MySQL题集(16)

MySQL 存储过程与触发器 一、初识MySQL的存储过程 Stored Procedure存储过程是数据库系统中一个十分重要的功能&#xff0c;使用存储过程可以大幅度缩短大SQL的响应时间&#xff0c;同时也可以提高数据库编程的灵活性。 存储过程是一组为了完成特定功能的SQL语句集合&#x…...

【算法基础】Dijkstra 算法

定义&#xff1a; g [ i ] [ j ] g[i][j] g[i][j] 表示 v i v_i vi​ 到 $v_j $的边权重&#xff0c;如果没有连接&#xff0c;则 g [ i ] [ j ] ∞ g[i][j] \infty g[i][j]∞ d i s [ i ] dis[i] dis[i] 表示 v k v_k vk​ 到节点 v i v_i vi​ 的最短长度&#xff0c; …...

使用 Flask 3 搭建问答平台(三):注册页面模板渲染

前言 前端文件下载 链接https://pan.baidu.com/s/1Ju5hhhhy5pcUMM7VS3S5YA?pwd6666%C2%A0 知识点 1. 在路由中渲染前端页面 2. 使用 JinJa 2 模板实现前端代码复用 一、auth.py from flask import render_templatebp.route(/register, methods[GET]) def register():re…...

pycharm如何debug for循环里面的错误值

一般debug时&#xff0c;在for循环里面的话&#xff0c;需要自己一步一步点。如果循环几百次那种就比较麻烦。此时可以采用try except的方式来解决 例子如下 #ptyhon debug for循环的代码 num[1,2,3,s,4] ans0 for i in num:try:ansiexcept:print(错误) print(ans) 结果如下&a…...

解决网页中的 video 标签在移动端浏览器(如百度访问网页)视频脱离文档流播放问题

问题现象 部分浏览器视频脱离文档流&#xff0c;滚动时&#xff0c;视频是悬浮出来&#xff0c;在顶部播放 解决方案 添加下列属性&#xff0c;可解决大部分浏览器的脱离文档流的问题 <videowebkit-playsinline""playsInlinex5-playsinlinet7-video-player-t…...

.Net--CLS,CTS,CLI,BCL,FCL

1.什么是CLS&#xff1f; 所以.NET专门为此参考每种语言(例如C# &#xff0c;VB&#xff0c;F#)并找出了语言间的共性&#xff0c;然后定义了一组规则&#xff0c;开发者都遵守这个规则来编码&#xff0c;那么代码就能被任意.NET平台支持的语言所通用。 而与其说是规则&#x…...

Stable Diffusion:质量高画风清新细节丰富的二次元大模型二次元插图

今天和大家分享一个基于Pony模型训练的二次元模型&#xff1a;二次元插图。关于该模型有4个不同的分支版本。 1.5版本&#xff1a;loar模型&#xff0c;推荐底模型niji-动漫二次元4.5。 xl版本&#xff1a;SDXL模型版本 mix版本&#xff1a;光影减弱&#xff0c;减少SDXL版本…...

数读MEME之争:以太坊获更高价值共识,抢占热点成Solana流量密码

在当前显著的加密牛市中&#xff0c;以太坊和Solana之间的竞争不仅在币价表现上显而易见&#xff0c;生态发展方面也备受关注。特别是在这轮MEME行情中&#xff0c;双方阵营的MEME代币呈现出不同的特点和趋势。 市场表现对比 以太坊的优势&#xff1a; 市场份额和认可度更高&…...

python的with语句

1.with语句的作用 在 Python 中&#xff0c;with 语句用于创建一个上下文管理器&#xff0c;以更简洁和安全的方式管理资源。 其主要优点是可以确保在代码块执行完毕后&#xff0c;相关资源能够被正确释放或清理&#xff0c;即使在代码块内部发生了异常。 以下是一个使用 with…...

Selenium原理深度解析

在自动化测试领域&#xff0c;Selenium无疑是最受欢迎和广泛使用的工具之一。它支持多种浏览器和操作系统&#xff0c;为开发人员和测试人员提供了强大的自动化测试解决方案。本文将深入探讨Selenium的工作原理&#xff0c;包括其架构、核心组件、执行流程以及它在自动化测试中…...

算法复杂度<数据结构 C版>

什么是算法复杂度&#xff1f; 简单来说算法复杂度是用来衡量一个算法的优劣的&#xff0c;一个程序在运行时&#xff0c;对运行时间和运行空间有要求&#xff0c;即时间复杂度和空间复杂度。 目录 什么是算法复杂度&#xff1f; 大O的渐近表达式 时间复杂度示例 空间复杂度…...

【XSS】

文章目录 0x01 简介0x02 XSS Payload用法XSS攻击平台及调试JavaScript 0x03 XSS绕过XSS漏洞防御策略 跨站脚本攻击&#xff0c;Cross Site Script。&#xff08;重点在于脚本script&#xff09; 有关XSS可以造成的 危害&#xff0c;见 0x02 XSS Payload用法 分类 反射型、存储…...

Go网络编程-RPC程序设计

gRPC 通信 RPC 介绍 RPC, Remote Procedure Call&#xff0c;远程过程调用。与 HTTP 一致&#xff0c;也是应用层协议。该协议的目标是实现&#xff1a;调用远程过程&#xff08;方法、函数&#xff09;就如调用本地方法一致。 如图所示&#xff1a; 说明&#xff1a; Servi…...

Linux 性能优化:轻松入门

文章目录 前言一、磁盘性能优化1、 磁盘 RAID 模式选择2、文件系统优化 二、优化 CPU1、性能监控 &#xff1a;2、进程优先级调整 &#xff1a;3、进程与 CPU 绑定 &#xff1a; 三、优化内存四、网络性能优化1、调整 TCP 缓冲区大小2、修改系统级别的文件描述符的数量3、调整 …...

C++相关概念和易错语法(22)(final、纯虚函数、继承多态难点)

1.final final在继承和多态中都可以使用&#xff0c;在继承中是指不想将自己被继承&#xff0c;在多态中是指不想该函数被重写&#xff0c;比较简单&#xff0c;下面是一些使用例子。 2.纯虚函数 当我们需要抽象一个类的时候&#xff0c;我们就需要用到纯虚函数。所谓抽象的类…...

状态管理的艺术:探索Flutter的Provider库

状态管理的艺术&#xff1a;探索Flutter的Provider库 前言 上一篇文章中&#xff0c;我们详细介绍了 Flutter 应用中的状态管理&#xff0c;以及 StatefulWidget 和 setState 的使用。 本篇我们继续介绍另一个实现状态管理的方式&#xff1a;Provider。 Provider优缺点 基…...

玩转HarmonyOS NEXT之IM应用首页布局

本文从目前流行的垂类市场中&#xff0c;选择即时通讯应用作为典型案例详细介绍HarmonyOS NEXT的各类布局在实际开发中的综合应用。即时通讯应用的核心功能为用户交互&#xff0c;主要包含对话聊天、通讯录&#xff0c;社交圈等交互功能。 应用首页 创建一个包含一列的栅格布…...

GPT-4o大语言模型优化、本地私有化部署、从0-1搭建、智能体构建

原文链接&#xff1a;GPT-4o大语言模型优化、本地私有化部署、从0-1搭建、智能体构建https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247608565&idx3&snd4e9d447efd82e8dd8192f7573886dab&chksmfa826912cdf5e00414e01626b52bab83a96199a6bf69cbbef7f7fe…...

记录些MySQL题集(4)

1、数据库的三范式是什么&#xff1f; 第一范式&#xff1a;列不可再分 第二范式&#xff1a;在第一范式的基础上&#xff0c;要求数据库表中的所有非主键列完全依赖于主键&#xff0c;而不是仅依赖于主键的一部分 第三范式&#xff1a;满足第二范式的基础上&#xff0c;所有…...

pdf提取其中一页怎么操作?提取PDF其中一页的方法

pdf提取其中一页怎么操作&#xff1f;需要从一个PDF文件中提取特定页码的操作通常是在处理文档时常见的需求。这种操作允许用户选择性地获取所需的信息&#xff0c;而不必操作整个文档。通过选择性提取页面&#xff0c;你可以更高效地管理和利用PDF文件的内容&#xff0c;无论是…...

godot使用ws

go服务端 package mainimport ("encoding/json""fmt""github.com/gorilla/websocket""net/http" )var upgrader websocket.Upgrader{ReadBufferSize: 1024,WriteBufferSize: 1024, }// 处理函数 func handleWebSocket(w http.Respo…...

Windows FFmpeg 开发环境搭建

FFmpeg 开发环境搭建 FFmpeg命令行环境搭建使用FFmpeg官方编译的库Windows编译FFmpeg1. 下载[msys2](https://www.msys2.org/#installation)2. 安装完成之后,将安装⽬录下的msys2_shell.cmd中注释掉的 rem set3. 修改pacman 镜像源并安装依赖4. 下载并编译源码 FFmpeg命令行环境…...

链路聚合概述

目录 技术背景&#xff1a; 基本概念&#xff1a; 链路聚合的工作模式&#xff1a; 简介&#xff1a; 1&#xff09;Manual Load-balance&#xff08;手动负载分担&#xff09; 简介&#xff1a; 配置实施&#xff1a; 2&#xff09;LACP&#xff08;链路聚合控制协议&#xff…...

【链表】算法题(二) ----- 力扣/牛客

一、链表的回文结构 思路&#xff1a; 找到链表的中间节点&#xff0c;然后逆置链表的后半部分&#xff0c;再一一遍历链表的前半部分和后半部分&#xff0c;判断是是否为回文结构。 快慢指针找到链表的中间节点 slow指针指向的就是中间节点 逆置链表后半部分 逆置链表后半部分…...

合成复用原则

合成复用原则 (Composite Reuse Principle, CRP) 合成复用原则&#xff08;Composite Reuse Principle, CRP&#xff09;&#xff0c;也被称为组合/聚合复用原则&#xff0c;是面向对象设计中的一条重要原则。它的核心思想是&#xff1a;优先使用对象组合/聚合&#xff0c;而不…...

安卓自带camera hal3 实例README.md翻译

最近&#xff0c;遇到一个这样的问题&#xff0c;临时了解下这个驱动实现架构和特点&#xff0c;翻译如下 V4L2相机HALv3 camera.v4l2库使用视频Linux 2&#xff08;V4L2&#xff09;接口实现了camera HAL v3。这使得它在理论上可以与各种设备配合使用&#xff0c;尽管V4L2的…...

ActiViz实战:ActiViz中的自己实现鼠标双击事件

文章目录 1、添加鼠标事件2、网上实现双击事件的方式3、增加双击的时间限制4、补充说明1、添加鼠标事件 已知在C#中观察者/命令模式会报错,正常添加鼠标事件如下: private void VtkInteractorStyleTest() {vtkInteractorStyle style = vtkInteractorStyle.New();style.LeftB…...

Linux-交换空间(Swap)管理

引入概念 在计算机中&#xff0c;硬盘的容量一般比内存大&#xff0c;内存&#xff08;4GB 8GB 16GB 32GB 64GB…&#xff09;&#xff0c;硬盘&#xff08;512GB 1T 2T…&#xff09;。 冯诺依曼的现代计算机结构体系里面的存储器就是内存 内存是一种易失性存储器&#xff0c…...

扫描某个网段下存活的IP:fping

前言&#xff1a; 之前用arp统计过某网段下的ip&#xff0c;但是有可能统计不全。网络管理平台又不允许登录。想要知道当前的ip占用情况&#xff0c;可以使用fping fping命令类似于ping&#xff0c;但比ping更强大。与ping需要等待某一主机连接超时或发回反馈信息不同&#x…...

【深度学习】PyTorch框架(3):优化与初始化

1.引言 在本文中&#xff0c;我们将探讨神经网络的优化与初始化技术。随着神经网络深度的增加&#xff0c;我们会遇到多种挑战。最关键的是确保网络中梯度流动的稳定性&#xff0c;否则可能会遭遇梯度消失或梯度爆炸的问题。因此&#xff0c;我们将深入探讨以下两个核心概念&a…...

php网站链接支付宝/怎样在网上做宣传

<!--实现搜索结果的关键词变色标注的程序四月 5th, 2006 在搜索得到的文本中&#xff0c;从第一个关键词出现的前50个字开始显示&#xff0c;把关键词替换为红色&#xff0c;这比单纯的用replace得到的显示结果更人性化一些&#xff0c;因为用replace的话一旦关键词出现在文…...

wordpress简单统计插件/seo建站公司推荐

引言 个别时候需要将当前文件&#xff08;文件自己&#xff09;拷贝到一个固定的目录&#xff08;项目组都熟悉的目录&#xff09;下面&#xff0c;可以使用 shell 脚本来实现此功能&#xff0c;比较简单。 可以首先通过两个小的例子来了解下面两个命令&#xff1a; basenam…...

广州网站推广排名/网址大全百度

一、将你要发布的moudle的build.gradle中添加代码&#xff0c;gradle的最后添加 PUBLISH_GROUP_ID com.zzti.fengyongge PUBLISH_ARTIFACT_ID imagepicker PUBLISH_VERSION 1.0 apply from: https://raw.githubusercontent.com/blundell/release-android-library/master/and…...

曲靖手机网站建设/网络营销专业如何

Java RMI基础Java远程方法调用&#xff0c;即Java RMI&#xff08;Java Remote Method Invocation&#xff09;是Java编程语言里&#xff0c;一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能…...

网络营销方式举个例子/温州seo公司

下面介绍无监督机器学习算法&#xff0c;与前面分类回归不一样的是&#xff0c;这个不知道目标变量是什么&#xff0c;这个问题解决的是我们从这些样本中&#xff0c;我们能发现什么。 这下面主要讲述了聚类算法&#xff0c;跟数据挖掘中的关联挖掘中的两个主要算法。 K均值算法…...

合理规划网站/搜狗输入法下载安装

前言 很多次小伙伴问到学习方法&#xff0c;我也很想写这样的一篇文章来跟大家讨论下关于学习方法这件事情。 其实学习方法这个事情&#xff0c;我没啥发言权&#xff0c;因为我自己本身都是没啥方法可言的&#xff0c;就瞎折腾那种&#xff0c;但是大家想看这样的一篇文章&a…...