当前位置:   article > 正文

MySql高级(教学版)_mysql高级教程

mysql高级教程

`一、索引、事务和锁

1、索引

1.1、简介
  • 什么是索引?
    • 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。
  • 为什么要有索引?
    • 索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要。
    • 索引优化应该是对查询性能优化最有效的手段了。善用索引能够轻易将查询性能提高好几个数量级。
    • 索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。
  • 索引的优劣势
    • 优势:可以快速检索,减少I/O次数,加快检索速度;根据索引分组和排序,可以加快分组和排序。
    • 劣势:索引本身也是表,因此会占用存储空间,一般来说,索引表占用的空间是数据表的1.5倍;索引表的维护和创建需要时间成本,这个成本随着数据量增大而增大;构建索引会降低数据表的修改操作(删除,添加,修改)的效率,因为在修改数据表的同时还需要修改索引表。
1.2、使用
1.创建/添加
  • 方法一:创建表的时候创建

    • CREATE TABLE mytable(
      	id int primary key not null,
      	username VARCHAR(16) not null,
      	index myindex(username)
      )
      
      • 1
      • 2
      • 3
      • 4
      • 5
  • 方法二:后期插入

    • -- 添加索引
      CREATE index myindex ON mytable(username);
      -- 或
      ALTER table mytable ADD INDEX myindex(username);
      
      • 1
      • 2
      • 3
      • 4
2.查看
  • -- 查看索引
    show index from mytable
    
    • 1
    • 2
3.删除
  • -- 删除索引
    drop index myindex ON mytable;
    -- 或
    ALTER TABLE mytable DROP INDEX myindex;
    
    • 1
    • 2
    • 3
    • 4
4.查看sql语句使用情况
  • -- explain 查询当前语句所使用的索引
    EXPLAIN select * from mytable where username = "张三"
    
    EXPLAIN select * from mytable where id =1
    
    • 1
    • 2
    • 3
    • 4
  • 运行结果如下

    • 在这里插入图片描述

    • 如果possible_keys为null则没使用索引

1.3、索引的使用规则
1.索引的分类

常见的索引类型有:主键索引、唯一索引、普通索引、全文索引、组合索引。

  1. 主键索引:即主索引,根据主键pk_clolum(length)建立索引,不允许重复,不允许空值;
  2. 唯一索引:用来建立索引的列的值必须是唯一的,允许空值;
  3. 普通索引:用表中的普通列构建的索引,没有任何限制;
  4. 全文索引:用大文本对象的列构建的索引;
  5. 组合索引:用多个列组合构建的索引,这多个列中的值不允许有空值。
2.适合建立索引的情况
  1. 主键自动建立唯一索引;
  2. 经常作为查询条件在WHERE或者ORDERBY语句中出现的列要建立索引;
  3. 作为排序的列要建立索引;
  4. 查询中与其他表关联的字段,外键关系建立索引;
  5. 高并发条件下倾向组合索引;
  6. 用于聚合函数的列可以建立索引,例如使用了max(column_1)或者count(column_1)时的column_1就需要建立索引。
3.不适合建立索引的情况
  1. 经常增删改的列不要建立索引;
  2. 表记录太少不要建立索引。只有当数据库里已经有了足够多的测试数据时,它的性能测试结果才有实际参考价值。如果在测试数据库里只有几百条数据记录,它们往往在执行完第一条查询命令之后就被全部加载到内存里,这将使后续的查询命令都执行得非常快–不管有没有使用索引。只有当数据库里的记录超过了1000条、数据总量也超过了MySQL服务器上的内存总量时,数据库的性能测试结果才有意义。
4.索引失效的情况
  1. 在组合索引中不能有列的值为NULL,如果有,那么这一列对组合索引就是无效的;
  2. 在一个SELECT语句中,索引只能使用一次,如果在WHERE中使用了,那么在ORDERBY中就不要用了;
  3. LIKE操作中,’%aaa%'不会使用索引,也就是索引会失效,但是‘aaa%’可以使用索引;
  4. 在索引的列上使用表达式或者函数会使索引失效;
  5. 在查询条件中使用不等于,包括<符号、>符号和!=会导致索引失效;
  6. 在查询条件中使用ISNULL或者ISNOTNULL会导致索引失效;
  7. 字符串不加单引号会导致索引失效;
  8. 在查询条件中使用OR连接多个条件会导致索引失效,除非OR链接的每个条件都加上索引;
  9. 如果排序的字段使用了索引,那么select的字段也要是索引字段,否则索引失效;
  10. 尽量不要包括多列排序,如果一定要,最好为这队列构建组合索引。

具体查看索引是否有效,可以使用explain命令查询。

2、事务

2.1、概念
  • 事务(Transaction):一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)。

  • 事务只和DML(增删改)语句有关,或者说DML语句才有事务。(DML)数据操纵语言。

  • 举例:银行转账,执行了两条修改语句,必须两条语句都执行成功才提交事务,否则一条成功,另一条失败就回滚事务。

2.2、MySql中事务的使用

在mysql中,事务的常规操作包括:开启事务提交事务回滚事务

1.开启事务
-- 开启事务
START TRANSACTION
-- 或
BEGIN TRANSACTION
  • 1
  • 2
  • 3
  • 4
2.提交事务
commit;
  • 1
3.回滚事务
rollback;
  • 1

4.举例

  • 手动开启事务,向t_act表中插入一行记录,提交事务。代码如下所示:

  • start transaction; -- 手动开启事务
    insert into t_act values(3,300); -- 更新操作
    commit; -- 提交事务,commit之后即改变底层数据库
    select * from t_act;
    
    • 1
    • 2
    • 3
    • 4
  • 手动开启事务,向t_act表中插入一行记录,回滚事务。代码如下所示:

  • start transaction; -- 手动开启事务
    insert into t_act values(4,200); -- 更新操作
    rollback; -- 回滚事务
    select * from t_act; -- 回滚事务会撤销更新操作,代码执行前后数据表中的数据没有发生任何变化。
    
    • 1
    • 2
    • 3
    • 4
2.3、事务的特征
  • 事务的四大特征(ACID):
    1. 原子性(A):事务是最小单位,不可再分;
    2. 一致性©:事务要求所有的DML语句操作的时候,必须保证同时成功或者同时失败;
    3. 隔离性(I):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰;
    4. 持久性(D):是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中)。
2.4、事务的隔离级别
  • 事务的隔离级别有4个:

    1. 读未提交(readuncommitted):事务A和事务B,事务A未提交的数据,事务B可以读取到,这里读取到的数据叫做“脏数据”。这种隔离级别最低,一般是在理论上存在,数据库隔离级别一般都高于该级别;
    2. 读已提交(readcommitted):事务A和事务B,事务A提交的数据,事务B才能读取到。这种隔离级别高于读未提交,可以避免“脏数据”。但如果事务A多次读取同一数据,而事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致,这种现象称为“不可重复读”;
    3. 可重复读(repeatableread):事务A和事务B,事务A提交之后的数据,事务B读不到事务A提交的数据。这种隔离级别高于读已提交,但如果事务A在修改数据的过程中(比如将所有记录状态设为1),如果事务B向同一张表中插入一条新记录(状态为0),事务A提交后再次查询表,会发现有一条记录状态没有改成1,好像发生了幻觉,这种现象称为“幻读”。可重复读是MySQL默认隔离级别;
    4. 串行化(serializable):事务A和事务B,事务A在操作数据库时,事务B只能排队等待。这种级别可以避免“幻读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,而不并发。
  • 事务隔离级别与一致性的关系也可以用下表表示:

    • 在这里插入图片描述
2.5、测试
表结构和数据:
create table tb_test(
	id int not null primary key auto_increment,
	text varchar(200) default null
)

insert tb_test(text) values('first row'),('second row'),('third row')

-------------------------------------------------------------------------------
未提交读(READ UNCOMMITED):
A会话:
start transaction;
update tb_test set text='1 row' where id=1

B会话:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
select * from tb_test where id=1;

A会话:
Rollback;
select * from tb_test

B会话:
select * from tb_test where id=1;

此时会发现B会话可以拿到A未提交的数据,即“脏数据”,如果拿着脏数据去参与运算,肯定会发生错误。
并且发现在B会话中两次查询的结果不一致,即“不可重复读”
接下来演示“幻读”,幻读即“两次查询的结果的行数不一致”

B会话:
commit;(注意:当开启了一个事务后,一定要以commitrollback结束事务)

A会话:
START TRANSACTION;
insert tb_test(text) values('forth row')

B会话:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
select * from tb_test;

A会话:
rollback

B会话:
select * from tb_test;
commit;


此时发现B会话拿到了A会话中添加的行,但是A会话最终回滚了该行数据,此类虚幻的数据即“幻读”


-------------------------------------------------------------------------------
已提交读(READ COMMITTED):
B会话:
select * from tb_test;  -- 观测数据

B会话:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM tb_test

A会话:
START TRANSACTION;
UPDATE tb_test SET TEXT='1 row' where id=1;

B会话:
SELECT * FROM tb_test

此时会发现B会话中查询到的依然是A修改之前的数据,避免了“脏读”出现。

A会话:
commit;

B会话:
SELECT * FROM tb_test	
commit;


当A会话提交事务后,数据真正被改写,此时B会话中已经查到了被修改后的数据。


-------------------------------------------------------------------------------
重复读(REPEATABLE READ):
A会话:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM tb_test

注意观测此时的A会话中第三行结果


B会话:
START TRANSACTION;
update tb_test set text='3 row' where id=3;
SELECT * FROM tb_test


 
注意观测此时的B会话中第三行结果

A会话:
SELECT * FROM tb_test
 
注意观测此时的A会话中第三行结果,与之前A会话的结果完全一致,不受B会话的任何影响

B会话:
commit;

A会话:
commit;
SELECT * FROM tb_test
 
当A会话提交事务后,得到的数据才与B会话一致。


-------------------------------------------------------------------------------
可串行化(SERIALIZEABLE):
可串行化隔离级别最高,不会出现脏读、幻读、不可重复读,但是由于独占性太高,会显著阻塞数据库并发性能。
A会话:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM tb_test

B会话:
update tb_test set text='2 row' where id=2;
此时会发现B会话纹丝不动。 

A会话:
commit;

此时B会话的命令会立刻执行成功。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
2.6、总结
  • 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为ReadCommitted,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

3、锁

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库显得尤其重要,也更加复杂。

3.1、乐观锁
  • 乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
  • 通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,并将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
  • 除了自己手动实现乐观锁之外,现在许多框架已经封装好了乐观锁的实现,如hibernate或JPA,需要时,可自行百度搜索“hibernate乐观锁”试试看。
3.2、悲观锁

与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

3.2.1、MySQL中悲观锁的实现主要有两种:共享锁(S)与排它锁(X),它们分别对应数据的读和写操作。

  1. 共享锁(S):也称为读锁,允许事务读取一行数据。例如事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
  2. 排它锁(X):也称写锁,允许事务删除或更新一行数据。例如事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。

注意:共享锁之间是兼容的,而排它锁与其他任意锁都不兼容。因此我们在数据库中可以并行读,但只能串行写,只有这样才不会发生线程竞争。

3.2.2、锁的粒度

锁的粒度就是你要锁定的范围是多大。使用锁时,锁定的东西越少,对其他竞争者影响越小,这个东西的大小就是锁粒度。

​ 例如卫生间里的洗漱台,马桶,浴缸,如果分别隔开(三个区域可以分别锁定,粒度小),就可以同时给三个人使用。你关上浴室的门,别人还是可以洗手的;但如果卫生间的区域不隔开,一个人进卫生间锁上门,其他人就进不去了。可见,锁的粒度越小,锁定的资源越少,对其他用户的影响就越小,当然,对系统的开销也越大。

锁的粒度分类:

  • 表级锁:开销小,加锁快,不会出现死锁。锁定粒度最大,适合查询为主的,只有少量按索引条件更新数据的操作。缺点是资源争用概率高;
  • 行级锁:开销大,加锁慢,会出现死锁。锁定粒度最小,适合大量按索引条件并发更新少量不同数据,同时又有并发查询的操作。使用行级锁定的主要是InnoDB存储引擎;
  • 页面锁:开销介于表锁与行锁之间,会出现死锁。

二、存储过程和循环控制语句(很少用)

1、简介

1.1、为什么要用存储过程?
  • MySQL5.0版本开始支持存储过程。
  • 大多数SQL语句都是针对一个或多个表的单条语句。并非所有的操作都那么简单。经常会有一个完整的操作需要多条语句才能完成。存储过程简单来说,就是==为以后的使用而保存的一条或多条MySQL语句的集合。==可将其视为批处理文件。虽然他们的作用不仅限于批处理。
  • 存储过程思想上很简单,就是数据库SQL语言层面的代码封装与重用。
1.2、优缺点
  • 优点:
    1. 通过把处理封装在容易使用的单元中,简化复杂的操作;
    2. 简化对变动的管理。如果表名、列名或业务逻辑有变化。只需要更改存储过程的代码,使用它的人员不会改自己的代码;
    3. 通常存储过程有助于提高应用程序的性能。当创建的存储过程被编译之后,就存储在数据库中。但是,MySQL实现的存储过程略有不同。MySQL存储过程按需编译。在编译存储过程之后,MySQL将其放入缓存中。MySQL为每个连接维护自己的存储过程高速缓存。如果应用程序在单个连接中多次使用存储过程,则使用编译版本,否则存储过程的工作方式类似于查询;
    4. 存储过程有助于减少应用程序和数据库服务器之间的流量,因为应用程序不必发送多个冗长的SQL语句,而只用发送存储过程的名称和参数;
    5. 存储的程序对任何应用程序都是可重用的和透明的。存储过程将数据库接口暴露给所有应用程序,以便开发人员不必开发存储过程中已支持的功能;
    6. 存储的程序是安全的。数据库管理员可以向访问数据库中存储过程的应用程序授予适当的权限,而不向基础数据库表提供任何权限。
  • 缺点:
    1. 如果使用大量存储过程,那么使用这些存储过程的每个连接的内存使用量将会大大增加。此外,如果您在存储过程中过度使用大量逻辑操作,则CPU使用率也会增加,因为MySQL数据库最初的设计侧重于高效的查询,不利于逻辑运算;
    2. 存储过程的构造使得开发具有复杂业务逻辑的存储过程变得更加困难;
    3. 很难调试存储过程。只有少数数据库管理系统允许您调试存储过程。不幸的是,MySQL不提供调试存储过程的功能;
    4. 开发和维护存储过程并不容易。开发和维护存储过程通常需要一个不是所有应用程序开发人员拥有的专业技能。这可能会导致应用程序开发和维护阶段的问题。

2、创建与调用

2.1、创建
  • 创建存储过程,代码如下所示:

    CREATE PROCEDURE mypro(in a int, in b int, out sum int)
    BEGIN
    	SET sum = a + b;
    END
    
    • 1
    • 2
    • 3
    • 4
  • 运行结果如下:

    在这里插入图片描述

  • 也可以在Navicat客户端“函数”节点下查看过程,如下图所示:

    在这里插入图片描述

2.2、调用
  • 语法如下:

    call mypro(1,2,@s);--调用存储过程
    select @s;--显示过程输出结果
    
    • 1
    • 2
  • 运行结果如下:

    在这里插入图片描述

3、语法解析

分析前面任务中的语法如下:

  • create procedure 用来创建过程;
  • mypro 用来定义过程名称;
  • (in a int,in b int,out sum int)表示过程的参数,其中in表示输入参数,out表示输出参数。类似于Java定义方法时的形参和返回值;
  • begin与end表示过程主体的开始和结束,相当于Java定义方法的一对大括号;
  • call用来调用过程,@s是用来接收过程输出参数的变量。

存储过程根据参数可分为四种类别:

  1. 没有参数的过程;
  2. 只有输入参数的过程;
  3. 只有输出参数的过程;
  4. 包含输入和输出参数的过程。

4、变量

MySQL中的存储过程类似java中的方法。既然如此,在存储过程中也同样可以使用变量。java中的局部变量作用域是变量所在的方法,而MySQL中的局部变量作用域是所在的存储过程。

4.1、定义
DECLARE var_name[,...] type [DEFAULT value]
  • 1
  • declare用于声明变量;

  • variable_name表示变量名称;

  • datatype为MySQL的数据类型;

  • default用于声明默认值;

  • 例如:

    declare name varchar(20) default "张三";
    
    • 1
4.2、赋值
SET 变量名 = 表达式值 [,variable_name = expression ...]
  • 1

在存储过程中使用变量,代码如下所示:

create PROCEDURE mypro2() -- 定义存储过程
BEGIN
	DECLARE name varchar(20); -- 定义变量
	set name='张三'; -- 给变量赋值
	select * from mytable where username = name;
END

CALL mypro2(); -- 调用过程
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5、流程控制语句

5.1、判断语句
if条件语句

IF语句包含多个条件判断,根据结果为TRUE、FALSE执行语句,与编程语言中的if、elseif、else语法类似。

  • 使用:

    -- if条件语句
    create PROCEDURE mypro3(in num int)
    BEGIN
    	if num<0 then -- 条件开始
    		select '负数';
    	elseif num=0 THEN
    		select '为0';
    	else
    		select '正数';
    	END IF; -- 条件结束
    end;
    
    call mypro3(-1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
case条件语句

case是另一个条件判断的语句,类似于编程语言中的choose、when语法。MySQL中的case语句有两种语法格式。

  • 使用

    -- case条件语句
    create PROCEDURE mypro4(in num int)
    BEGIN
    	CASE
    		WHEN num<0 then select '负数';
    		WHEN num=0 then select '为0';
    	ELSE select '正数';
    	END CASE;
    END
    
    call mypro4(5)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
5.2、循环语句
while循环语句

while语句的用法和java中的while循环类似。

  • 使用:

    -- while循环
    create PROCEDURE mypro5(out sum int)
    BEGIN
    	DECLARE i int DEFAULT 0;
    	set sum=0;
    	WHILE i<10 do -- 循环开始
    		set i = i + 1;
    		set sum = sum + i;
    	end WHILE; -- 循环结束
    end
    
    -- 调用过程
    call mypro5(@sum);
    select @sum
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
repeat循环语句

repeat语句的用法和java中的do…while语句类似,都是先执行循环操作,再判断条件,区别是repeat表达式值为false时才执行循环操作,直到表达式值为true停止。

  • 使用

    -- repeat循环
    create procedure mypro6(out sum int)
    BEGIN
    	declare i int default 0;
    	set sum = 0;
    	REPEAT -- 循环开始
    		set i=i+1;
    		set sum = sum+i;
    	UNTIL i>=10
    	end REPEAT;
    END
    
    call mypro6(@sum);
    select @sum
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
loop循环语句

循环语句,用来重复执行某些语句。执行过程中可使用leave语句或iterate跳出循环,也可以嵌套IF等判断语句。

  • leave语句效果相当于java中的break,用来终止循环;

  • iterate语句效果相当于java中的continue,用来结束本次循环操作,进入下一次循环。

  • 使用:

    -- loop循环语句
    create procedure mypro7(out sum int)
    BEGIN
    	DECLARE i int default 0;
    	set sum = 0;
    	loop_tcc:LOOP -- 循环开始
    		set i = i+1;
    		set sum = sum+i;
    		IF i>=10 THEN
    			LEAVE loop_tcc; -- 离开圈圈
    		end if;
    	end LOOP loop_tcc; -- 循环结束
    END
    
    call mypro7(@sum);
    select @sum
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

6、存储过程的管理

存储过程的管理主要包括:显示过程、显示过程源码、删除过程。比较简单的方式就是利用navicat客户端工具进行管理,鼠标点击操作即可,如下图所示:

在这里插入图片描述

6.1、显示存储过程
  • 显示全部存储过程,代码如下所示:

    -- 显示所有存储过程
    SHOW PROCEDURE STATUS;
    
    • 1
    • 2
  • 结果如下:

    在这里插入图片描述

  • 显示特定数据库的存储过程,代码如下:

    -- 显示特定数据库的存储过程
    SHOW PROCEDURE STATUS WHERE db = 'mysql1';
    
    • 1
    • 2
  • 结果如下:

    在这里插入图片描述

  • 显示特定模式的存储过程,要求显示名称中包含“my”的存储过程,代码如下所示:

    -- 显示特定模式的存储过程
    SHOW PROCEDURE STATUS WHERE name like '%my%';
    
    • 1
    • 2
6.2、显示存储过程源码
-- 显示存储过程源码
SHOW CREATE PROCEDURE mypro;
  • 1
  • 2

结果:

在这里插入图片描述

6.3、删除存储过程
drop PROCEDURE mypro;
  • 1

三、视图

1、简介

1.1、为什么要使用视图?
  1. 从三大范式当中,我们知道一张表只存一种实体的数据,但现实业务往往是,需要多个表的数据关联呈现的,并且某些固定的列会被频繁的访问,视图可以避免频繁的编写这些关联查询语句;
  2. 某些人可能只允许表中的部分列,不能将整个表的列或数据行暴露出来,我们可以为这些特定的人创建一个视图,把权限给到这些人,起到对基表中的其他列和数据行进行安全保护;

视图中存储了可以返回结果集的sql查询语句,当用在from子句时,内部的sql查询语句就会被执行。

1.2、什么是视图?

MySQL视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。

1.3、视图的优缺点
  • 优点:

    1. 简单化,数据所见即所得;
    2. 安全性,用户只能查询或修改他们所能见到得到的数据;
    3. 逻辑独立性,可以屏蔽真实表结构变化带来的影响。
  • 缺点

    1. 性能相对较差,从视图查询数据可能会很慢,特别是如果视图是基于其他视图创建的;
    2. 修改不方便,特别是复杂的聚合视图基本无法修改。

2、使用

  • 基于单表创建视图

    -- 创建视图
    create view view1
    AS
    select studentname,sex,age from student
    
    • 1
    • 2
    • 3
    • 4
    • 创建视图的语法只是在原来的select语句前添加create view 视图名 as就可以了。

    • 创建带别名的视图

      -- 创建带别名的视图
      create view view2(姓名,性别,年龄)
      AS
      select studentname,sex,age from student
      
      select * from view2
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 结果如下:

      在这里插入图片描述

  • 基于多表创建视图

    -- 创建多表的视图
    create view view3
    as
    select s.studentname,s.sex,s.age,c.clazzname
    from student s join clazz c on s.clazzid=c.clazzid
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

select * from view3


- 结果如下:

  ![1645779652582](E:\SrpingNotePad\img\1645779652582.png)

### 3、视图更新数据

在MySQL中,视图不仅是可查询的,而且是可更新的。这意味着您可以使用INSERT或UPDATE语句通过可更新视图插入或更新基表的行。另外,您可以使用DELETE语句通过视图删除底层表的行。

​	但是,要创建可更新视图,定义视图的SELECT语句不能包含以下任何元素:

1. 聚合函数;
2. distinct子句;
3. groupby子句;
4. having子句;
5. union和unionall子句;
6. 外连接;

**注意:不建议使用基于多表创建的视图进行更新操作。**

- 测试:

```mysql
-- 创建单表视图
create view view1
AS
select studentname,sex,age from student

-- 创建多表的视图
create view view3
as
select s.studentname,s.sex,s.age,c.clazzname
from student s join clazz c on s.clazzid=c.clazzid

select * from view1
-- 基于单表视图的添加(成功)
insert into view1 values('张三','女',30);

-- 基于单表视图的修改(成功)
update view1 set age = age+1;

select * from view3;
-- 基于多表视图的添加(失败)
insert into view3 values('李四','女',30);

-- 基于多表视图的修改(成功)
update view3 set age = age+1;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

4、WITH CHECK OPTION子句

有时候,创建一个视图来显示表的部分数据。然而,简单视图是可更新的,因此可以更新通过视图不可见的数据。此更新使视图不一致。为了确保视图的一致性,在创建或修改视图时使用WITHCHECKOPTION子句。

  • 测试:

    -- 创建一个年龄只能在30岁以上的视图
    create view view4
    as
    select studentname,sex,age from student where age>=30
    
    -- 虽然添加成功了,但是不合理view4视图里看不到年龄在30岁以下的数据
    -- 从示例结果中可以看到,新插入的学生在视图中是查不到的,为了确保视图的一致性,用户只能显示或更新通过视图可见的数据,则在创建或修改视图时使用WITH CHECK OPTION。
    insert into view4 values("王五","男",21);
    
    -- 创建一个年龄只能在30岁以上的视图2 关键字:WITH CHECK OPTION
    create view view5
    as
    select studentname,sex,age from student where age>=30 WITH CHECK OPTION
    
    -- 添加失败,因为年龄小于30岁
    insert into view5 values("王五","男",21);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    没有特殊理由的情况下,建议都带上WITHCHECKOPTION。

5、管理

5.1、利用可视化工具
  • 视图管理最简单的方式就是通过navicat客户端工具鼠标点击操作。

    在这里插入图片描述

5.2、查看视图定义
-- 查看视图定义
show create view view2;

  • 1
  • 2
  • 3
  • 结果:

    在这里插入图片描述

5.3、查看视图

视图本质上也是表,因此查看视图可以使用showtables命令查看。

-- 展示视图
show tables

  • 1
  • 2
  • 3
  • 结果:

    在这里插入图片描述

5.4、修改或替换视图

使用alter view替换create view就可以实现视图的修改操作,还可以使用CREATE OR REPLACE VIEW语句来创建或替换现有视图。如果一个视图已经存在,MySQL只会修改视图。如果视图不存在,MySQL将创建一个新的视图。

-- 修改视图
ALTER view view1
AS
select studentname,sex,age from student

  • 1
  • 2
  • 3
  • 4
  • 5
5.5、删除视图

使用drop view 视图名命令就可以删除视图了。

-- 删除视图
drop view view1

  • 1
  • 2
  • 3

四、函数

1、简介

MySQL中的函数和JavaScript中的函数作用类似,就是执行特定任务的代码块。

其实,大家已经有过在MySQL中使用函数的经验了,比如我们获得系统时间,可以用now()函数,求平均值可以用avg()函数等等。这些系统定义好的函数我们称为系统函数,可以直接拿来使用,但有些时候我们需要完成特定功能,就需要自己定义函数。用户自己定义的函数,称为自定义函数。

2、使用

2.1、创建函数
  • 语法:

    create function 函数名([参数列表]) returns数据类型
    begin
    	sql语句;
    	return 值;
    end
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 创建无参函数,返回学号为“3”的学生姓名。代码如下所示:

    -- 创建函数 查询学号为3的学生姓名
    create FUNCTION myfun1() returns varchar(20)
    BEGIN
    	DECLARE name varchar(20);
    	select studentname into name from student where studentid=3;
    	return name;
    end
    
    -- 调用函数
    select myfun1();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 结果:

      在这里插入图片描述

  • 创建带参数的函数 根据学号查询学生姓名

    -- 创建带参数的函数 根据学号查询学生姓名
    create FUNCTION myfun2(sid int) returns varchar(20)
    BEGIN
    	DECLARE name varchar(20);
    	select studentname into name from student where studentid=sid;
    	return name;
    end
    
    select myfun2(2);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

注意语法中的几个要点:

  1. 函数名后面有一对小括号,括号内可以填写或不填参数,但括号不能省略;
  2. 小括号后必须跟returns,returns后跟返回值类型,类型必须是MySQL中的类型;
  3. 函数主体放在begin…end内,end前要return与前面returns后跟的类型相同的值;
  4. select查询结果也可以用来给变量赋值,但是需要用into关键词。

3、管理

3.1、显示所有函数
-- 显示所有函数
show function STATUS

  • 1
  • 2
  • 3
  • 结果:

    在这里插入图片描述

3.2、显示函数源码
-- 显示函数源码
show create function myfun1

  • 1
  • 2
  • 3
  • 结果:

    在这里插入图片描述

3.3、删除函数
-- 删除函数
drop function myfun1

  • 1
  • 2
  • 3

4、函数与过程的区别

函数和过程统称为存储在数据库中的实现特定功能的代码块,但是它们之间也有一些区别:

  1. 返回值不同:函数必须有返回值,且仅返回一个结果值;过程可以没有返回值,但是能返回结果集;
  2. 调用时的不同:函数使用select进行调用;过程使用call进行调用;
  3. 参数的不同:函数的参数都是in参数;过程可以传递in\out\inout参数。

五、触发器

1、简介

​ 触发器是和表关联的特殊的存储过程,可以在插入,删除或修改表中的数据时触发执行,比数据库本身标准的功能有更精细和更复杂的数据控制能力。

  • 触发器的有点:
    1. 安全性:可以基于数据库的值使用户具有操作数据库的某种权利。例如不允许下班后和节假日修改数据库数据;
    2. 审计:可以跟踪用户对数据库的操作;
    3. 实现复杂的数据完整性规则。例如,触发器可回退任何企图吃进超过自己保证金的期货;
    4. 提供了运行计划任务的另一种方法。例如,如果公司的帐号上的资金低于5万元则立即给财务人员发送警告数据。

2、使用

  • 语法:

    • 创建触发器的技巧就是记住触发器的四要素:

      • 监控地点:table
      • 监控事件:insert/update/delete
      • 触发时间:after/before
    • 语法如下:

      CREATE TRIGGER trigger_name trigger_time trigger_event ON tb_name FOR EACH ROW trigger_stmt
      
      
      • 1
      • 2
      • trigger_name:触发器的名称;
      • tirgger_time:触发时机,为BEFORE或者AFTER;
      • trigger_event:触发事件,为INSERT、DELETE或者UPDATE;
      • tb_name:表示建立触发器的表名,在哪张表上建立触发器;
      • trigger_stmt:触发器的程序体,可以是一条SQL语句或者是用BEGIN和END包含的多条语句;
      • FOR EACH ROW表示任何一条记录上的操作满足触发事件都会触发该触发器。

注意:对同一个表相同触发时间的相同触发事件,只能定义一个触发器。

  • 使用

    • 假如你正在开发一个网上购物的应用,使用的数据库(shop)中有一张商品表(product)和订单表(orders),表结构及测试数据如下所示:

      CREATE TABLE `orders` (
      `oid` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
      `pid` int(11) NOT NULL COMMENT '商品编号',
      `num` int(11) NOT NULL COMMENT '购买数量',
      PRIMARY KEY (`oid`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      CREATE TABLE `product` (
      `pid` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品编号',
      `pname` varchar(255) NOT NULL COMMENT '商品名称',
      `num` int(11) NOT NULL COMMENT '库存',
      PRIMARY KEY (`pid`)
      ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
      INSERT INTO `product` VALUES ('1', 'p30', '10');
      INSERT INTO `product` VALUES ('2', 'mate20', '50');
      INSERT INTO `product` VALUES ('3', 'nova5', '100');
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 当用户购买商品时,需要往订单表插入订单记录,同时更新商品表库存信息。例如用户买了3个p30,我们需要做两件事:

      • 往订单表插入一条记录:

        insert into orders values(null,1,3);
        
        
        • 1
        • 2
      • 更新 p30 的库存:

        update product set num = num-3 where pid=1;
        
        
        • 1
        • 2
    • 这两条 sql 语句实现的是一个业务操作,从数据库的层面来讲,这样的业务我们就可以用触发器来处理。

    • 任务: 创建触发器,当用户购买 p30 时,同时更新 p30 库存记录,代码如下所示:

      -- 创建触发器
      create TRIGGER mytg1
      after insert on orders -- 在orders表添加之后触发
      for each row -- 针对每一行(固定写法)
      BEGIN
      	update product set num = num - 3 where pid=1;
      end
      
      -- 往订单表插入记录
      insert into orders values(null,1,3)
      
      -- 查询商品表商品库存更新情况
      select * from product
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 结果:

        在这里插入图片描述

      • 测试数据商品表p30有10台,当订单表插入3台p30的订单记录后,触发器就会执行更新商品表的操作,使p30库存减少3。当然这里还存在一个问题,因为代码中的数值是写死的,无论用户买什么商品,买几个,每次购买都会让p30的库存减3,所以我们需要将固定数值替换成变量,这就要用到触发订单表触发器的新纪录。

3、触发器新旧记录

3.1、概念

MySQL中定义了NEW和OLD,用来表示触发器的所在表中,触发了触发器的那一行数据:

  • 在INSERT型触发器中,NEW用来表示将要(BEFORE)或已经(AFTER)插入的新数据;
  • 在UPDATE型触发器中,OLD用来表示将要或已经被修改的原数据,NEW用来表示将要或已经修改为的新数据;
  • 在DELETE型触发器中,OLD用来表示将要或已经被删除的原数据。
3.2、使用
  • 创建触发器,当用户购买商品时,同时更新对应商品库存记录,代码如下所示:

    -- 删除之前的触发器
    drop trigger if EXISTS mytg1
    
    -- 创建添加的触发器
    create TRIGGER mytg1
    after insert on orders -- 在orders表添加之后触发
    for each row -- 针对每一行(固定写法)
    BEGIN
      -- 会把新添加的数据放到new里面,使用新数据的时候只需要new.列名
    	update product set num = num - new.num where pid=new.pid;
    end
    
    -- 往订单表插入记录(id为1的商品库存减3)
    insert into orders values(null,1,3)
    
    -- 插入其他商品记录进行测试(id为2的商品库存减5)
    insert into orders values(null,2,5)
    
    -- 查询商品表商品库存更新情况
    select * from product
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

用户下订单,同时更新商品库存的问题已经解决了,但是问题并不总是那么简单,一个完整的应用程序还要考虑很多问题。比如用户不想要了,撤销订单,商品的库存是否要恢复呢?

  • 创建触发器,当用户删除订单时,同时更新对应商品库存记录,代码如下所示:

    -- 创建删除的触发器
    create trigger mytg2
    after delete on orders -- 在orders表数据被删除之后触发
    for each ROW
    BEGIN
    	-- old代表被删除的数据 当订单表数据被删除时,对应商品的库存就会加回来
    	update product set num = num+old.num where pid=old.pid;
    END
    
    -- 删除订单记录
    delete from orders where oid=7
    
    -- 查询商品表商品库存更新情况
    select * from product
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

4、before和after的区别

​ 两者在一般的触发器中并没有什么区别,但是有的时候有区别,比如刚才的案例中,我们细想一下还会发现一个问题,如果我们购买的商品数量超过库存会怎样?

  • -- 往订单表插入记录
    insert into orders values(null,1,20);
    -- 查询商品表商品库存更新情况
    select * from product;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 结果:

      在这里插入图片描述

​ P30的库存被买成负数了,这明显是错误的,想要避免这个问题,就需要使用before触发器,下订单前,先查看商品库存,如果购买数量超过库存,则修改订单数量为商品最大库存。

  • 创建触发器,当用户删除订单时,同时更新对应商品库存记录,当订单商品数量超过库存时,修改订单数量为最大库存,代码如下所示:

    -- 删除创建的添加触发器,防止两个添加的触发器同时触发
    drop trigger if EXISTS mytg3
    -- 如果订单的商品数量超过库存,则把订单的数量变为库存的最大值
    create trigger mytg3
    before insert on orders -- 在订单表添加之前执行
    for EACH ROW
    BEGIN
    	DECLARE n int default 0; -- 定义变量n
    
    	-- 把订单的商品库存数量查询到赋值给变量n
    	select num into n from product where pid=new.pid;
    
    	-- 如果订单数量大于对应商品的库存数量则把订单的数量改为对应商品库存的最大数量
    	IF new.num>n then
    		set new.num=n;
    	end if;
    
    	update product set num=num-new.num where pid=new.pid;
    END
    
    -- 添加订单数量大于商品库存测试
    insert into orders values(null,1,20)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 结果:

      在这里插入图片描述

  • 在这里插入图片描述

​ 从运行结果可以看到,虽然用户购买了20台p30,但实际只购买了10台,并且商品的库存也变为0,不会出现负数了。

5、管理

  • 可以右键表,点击设计表,然后在打开的新页面的上方点击触发器进行管理。

在这里插入图片描述

5.1、删除
drop trigger if EXISTS 触发器名称

  • 1
  • 2
5.2、查看
-- 查看触发器
show triggers

  • 1
  • 2
  • 3

六、游标(很少用)

1、简介

游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行相应的操作。游标有下面这些特征:

  • 游标是只读的,也就是不能更新它;
  • 游标是不能滚动的,也就是只能在一个方向上进行遍历,不能在记录之间随意进退,不能跳过某些记录;
  • 避免在已经打开游标的表上更新数据。

2、使用

  • 创建游标:

    创建游标的语法包含四个部分:

    1. 定义游标:declare 游标名 cursor for select 语句
    2. 打开游标:open 游标名
    3. 获取结果:fetch 游标名 into 变量名[,变量名]
    4. 关闭游标:close 游标名
  • 使用:

    • 创建一个过程p1,使用游标返回test数据库中student表的第一个学生信息。代码如下所示:

      -- 定义存储过程 使用游标
      create PROCEDURE p1()
      BEGIN
      	-- 定义三个变量用于存放查询出来的三个列
      	DECLARE id int;
      	declare name varchar(20);
      	-- 定义的变量名不能和查询的列名一致,不然查不出来
      	declare sage int;
      	-- 声明游标
      	declare mc CURSOR for select studentid,studentname,age from student;
      	-- 打开游标
      	open mc;
      	-- 提取结果
      	FETCH mc into id,name,sage;
      	-- 显示获取的结果
      	select id,name,sage;
      	-- 关闭游标
      	close mc;
      END
      
      -- 调用过程
      call p1();
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 结果:

        在这里插入图片描述

    • 在test数据库创建一个student2表,创建一个过程p2,使用游标提取student表中所有学生信息插入到student2表中。代码如下所示:

      -- 把查出来的数据循环添加的新表中
      create PROCEDURE p2()
      BEGIN
      	-- 定义变量
      	declare id int;
      	declare name varchar(20);
      	declare sage int;
      	-- 声明游标
      	declare mm CURSOR for select studentid,studentname,age from student;
      	-- 打开游标
      	open mm;
      	-- 提取结果
      	LOOP -- 循环提取数据
      		FETCH mm into id,name,sage;
      		-- 将提取的每一行数据插入到student2中
      		insert into student2 values(id,name,sage);
      	end LOOP;
      	-- 关闭游标
      	close mm;
      end
      
      call p2();
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    • 虽然数据都插入到student2表中了,但是在调用p2的时候会报错。具体解决方法请查看下图:

      在这里插入图片描述

七、MySql技巧与优化(重要)

1、查找重复记录

  • 由于各种原因,数据库中的重复事件发生很多。查找重复值是使用数据库时必须处理的重要任务之一。例如在一个购物应用中,商品表信息如下所示:

    在这里插入图片描述

  • 使用sql语句查询表中重复的商品记录,代码如下所示:

    -- 使用sql语句查询表中重复的商品记录,代码如下所示:
    -- 使用分组以后查询的只能是被分组的列和聚合函数,having后跟分组的条件
    select pname,count(pname)
    from product
    group by pname
    having count(pname)>1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 结果:

      在这里插入图片描述

  • 任务二、使用sql语句查询表中商品名称和数量都重复的记录,代码如下所示:

    -- 使用sql语句查询表中商品名称和数量都重复的记录,代码如下所示:
    select pname,num,count(*)
    from product
    group by pname,num
    having count(pname)>1 and count(num)>1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 结果:

      在这里插入图片描述

2、删除重复记录

​ 在上一小节中,我们展示了如何在表中找到重复的值。当确定了表中有重复的行,您可能需要删除它们来清理这些不必要的数据。

  • 任务一、使用sql语句删除商品名称重复的记录,只保留id最大的记录,代码如下所示:

    -- 使用sql语句删除商品名称重复的记录,只保留id最大的记录,代码如下所示:
    delete p1 from product p1 inner join product p2
    where p1.pname=p2.pname and p1.pid<p2.pid;
    -- 查询商品表
    select * from product;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 结果:

      在这里插入图片描述

​ 删除后再查询商品表,发现id为1和4的p30记录都被删掉了。如果要保留id最小的记录,把p1.pid<p2.pid中的小于号,改成大于号就行了。

3、选择随机记录

有些业务需要从表中选择随机记录,比如:

  1. 在博客中选择一些随机的帖子,并在侧边栏中显示;

  2. 在店铺商品中选择一些随机的商品,并推荐到侧边导购栏;

  3. 在画廊中选择随机图片,并将其用作精选照片。

  • 任务一、使用sql语句从student表中随机选择1名学生记录,代码如下所示

    -- 使用sql语句从student表中随机选择1名学生记录,代码如下所示:
    -- rand():随机排序 limit:取第一条,取3条:limit 3
    select * from student
    order by rand()
    limit 1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 运行两次结果分别如下:

      在这里插入图片描述

在这里插入图片描述

​ 从结果中看到,两次查询获得的学生记录是不一样的。实现原理是rand()函数为表中每一行记录生成一个随机值,order by通过这个随机值进行排序,最后limit只返回第一行。由于每次rand()的值都是不一样的,所以最终返回的结果也不一样。

4、选择第n个最高记录

使用MAXMIN函数可以轻松选择数据库表中最高或最低的记录。但是,选择第n个最高记录有点棘手。

  • 任务一、查询库存第2多的商品信息,代码如下所示:

    -- 查询库存第2多的商品信息,代码如下所示:
    -- 先降序排一下取前两条,然后把查出来的结果当做一张表进行升序排序取第一条
    select * from 
    (select * from product order by num desc limit 2) as t1
    order by num ASC
    limit 1;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 运行结果如下:
      在这里插入图片描述
  • 先按库存降序排列,查询库存最多的2个商品;再以前面查询结果按照升序排列,取第1个记录。当然这个过程也可以简化为以下代码:

    -- 简化 limit n-1,1 查询排序后的第几位	n-1需要提前自己计算出具体数值
    select * from product order by num limit 1,1
    
    
    • 1
    • 2
    • 3

5、比较两个表的数据

  • 任务一、现有两张表,请你查询出A1字段中,存在t_a表,但是不存在t_b表的数据,表数据如下图所示:

    在这里插入图片描述

在这里插入图片描述

  • 因为两张表数据量都比较少,我们通过观察不难发现,查询结果如下图所示:

在这里插入图片描述

  • 代码如下:

    -- 现有两张表,请你查询出A1字段中,存在t_a表,但是不存在t_b表的数据,表数据如下图所示:
    select * from t_a where a1 not in (select a1 from t_b)
    
    
    • 1
    • 2
    • 3
  • 上面代码先通过子查询得到t_b表中a1字段的所有值,然后在外部查询中使用not in进行排除。这种方式本身没有错,但是在大量数据(百万级以上)的情况下,尤其是A1字段带有索引的情况下,查询速度会非常慢。除了not in还可以使用left join实现,代码如下所示:

    -- 上面那个数据大的话查询慢,没使用索引,优化:
    select t_a.* from t_a left join t_b 
    on t_a.a1=t_b.a1 
    where t_b.b1 is null
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 结果:

      在这里插入图片描述

    • 上面代码也可以得到相同的结果,但left join的效率比not in要高很多,并且不会让A1字段的索引失效。

    • 注意:代码中有两个条件,分别跟在on和where后面,能不能把where改成and?答案是不能,on和where后面都可以跟条件,但是在left join中效果是不一样的,on后面跟连接条件,先做表连接,where是对连接查询结果再进行筛选。在inner join中,因其机制不同,条件放在on后面还是where后面查询结果虽然是一样的,但执行原理和效率上也有一些区别。

6、行转列!

  • 现在有一张成绩表,表结构及数据如下所示:

    在这里插入图片描述

  • 任务一、为了方便查看每个同学的成绩,使用sql语句查询出如下结果:

    在这里插入图片描述

    • 代码如下:

      -- 行转列 为了方便查看每个同学的成绩,使用sql语句查询出如下结果:
      select user_name,
      	MAX(CASE course WHEN '数学' THEN score ELSE 0  END) as 数学,
      	MAX(CASE course WHEN '语文' THEN score ELSE 0  END) as 语文,
      	MAX(CASE course WHEN '英语' THEN score ELSE 0  END) as 英语,
      	sum(score) as 总分
      from test_tb_grade 
      group by user_name
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

7、exists查询

EXISTS关键字后面的参数是一个任意的子查询,系统对子查询进行运算以判断它是否返回行,如果至少返回一行,那么EXISTS的结果是TRUE,此时外层查询语句将进行查询;如果子查询没有返回任何行,那么EXISTS返回的结果是FALSE,此时外层语句将不进行查询。

  • 任务一、查询成绩表中科目编号为2的考试成绩中是否存在不及格的学生,如果存在不及格的学生就将参加科目编号2考试的学生编号和成绩全部查询显示出来,代码如下所示:

    -- exists 查询 返回true将外层查询语句进行查询,false则不查询外层语句
    -- 查询成绩表中科目编号为2的考试成绩中是否存在不及格的学生,
    -- 如果存在不及格的学生就将参加科目编号2考试的学生编号和成绩全部查询显示出来,
    -- 代码如下所示:
    select * from exam where subid=2 and exists
    (select * from exam where score<60)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

8、all、any、some查询

ALL用在子查询前,通过比较运算符将一个表达式或列的值与子查询返回的一列值中的每一行进行比较,只要有一次比较的结果为FALSE,则ALL测试返回FALSE

  • 任务一、查询成绩比科目编号为“1”的这门课程的所有成绩都大的学生考试信息,代码如下所示:

    -- 查询成绩比科目编号为“1”的这门课程的所有成绩都大的学生考试信息,代码如下所示:
    -- 最大值为80
    select * from exam where score>
    (select max(score) from exam where subid=1)
    
    -- 或 any
    select * from exam where score>all(select score from exam where subid=1)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 结果:

      在这里插入图片描述

    • 在这里,>ALL表示大于每一个值。换句话说,它表示大于最大值。例如,>ALL(1,2,3)表示大于3。ANY与子查询在一起使用时,按照比较运算符、表达式或字段对子查询的结果的每一行进行一次计算和比较。只要有一次满足条件,那么ANY的结果就是真。

  • 任务二、查询成绩比科目编号为“1”的任意一个成绩都大的考试信息,代码如下所示:

    -- 查询成绩比科目编号为“1”的任意一个成绩都大的考试信息,代码如下所示:any
    select * from exam where score>any(select score from exam where subid=1)
    
    
    • 1
    • 2
    • 3
    • 结果:

      在这里插入图片描述

    • all取比所有数都大的数,any取比随意一个数大的数 all:所有 any:任意

    • all(1,2,3)相当于大于3 >any(1,2,3)相当于大于1就满足。

  • 注意:=ANY运算符与IN等效。<>ANY运算符则不同于NOT IN<>ANY(A,B,C)表示不等于A,或者不等于B,或者不等于C。NOT IN(A,B,C)表示不等于A、不等于B并且不等于C。<>ALLNOT IN表示的意思相同。

9、union合并查询

MySQL UNION操作符用于连接两个以上的SELECT语句的结果组合到一个结果集合中并删除重复的数据。

  • 任务一、在test数据库中有一张学生表student和一张教师表teacher,表结构及测试数据如下所示:

    在这里插入图片描述

    • 要求查询所有师生的信息,代码如下所示:

      -- union合并查询
      -- 在test数据库中有一张学生表student和一张教师表teacher,要求查询所有师生的信息
      select * from student
      union
      select * from teacher
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 结果:

        在这里插入图片描述

    • 代码中使用union将两个表合并到了一起,但是发现一个问题,学生表和老师表中都有一个Jack的记录,合并结果中却只有一个jack记录。这是因为union在合并时会删除重复记录,相当于distinct,如果不想去重,可以使用union all

      -- union默认去重,union all不去重
      select * from student
      union all
      select * from teacher
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 结果:

        在这里插入图片描述

  • 注意:使用union查询有以下几个要点:

    • union联合的两个select必须拥有相同数量的列;
    • 列必须有相似的数据类型;
    • 列的顺序必须相同;
    • union因为要去重,效率远不如union all

10、计算列

​ 储存在表中的数据不都是计算机应用程序所需要的,我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。这就是计算字段要发挥的作用。

  • 任务一、在test数据库中有一张users表,表结构及测试数据如下:

    在这里插入图片描述

    • 在存储用户信息时,姓和名是分开的,现在要求通过sql查询出用户信息,要求姓名用一个字段显示,代码如下:

      -- 计算列
      -- 在存储用户信息时,姓和名是分开的,
      -- 现在要求通过sql查询出用户信息,要求姓名用一个字段显示,代码如下:
      select * from users
      
      -- concat拼接
      select id,CONCAT(first_name,last_name) as name from users
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 结果:

        在这里插入图片描述

    计算列除了拼接字符串,还可以进行算术计算。

  • 任务二、在test数据库中有一张goods表,表结构及测试数据如下:

    在这里插入图片描述

    • price字段表示单价,discount字段表示折扣,要求查询出商品信息,并计算出商品的售价(即单价*折扣),代码如下:

      -- price字段表示单价,discount字段表示折扣,
      -- 要求查询出商品信息,并计算出商品的售价(即单价*折扣),代码如下:
      select * from goods
      
      select id,name,price*discount as 折扣价,stock from goods
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 结果:

        在这里插入图片描述

11、排名查询

​ 在sqlserveroracle数据库中,都有系统定义的排名函数,很遗憾,mysql中目前还没有对排名查询的支持,这就需要我们自己去实现排名查询。

  • 任务一、在schooldb数据库中有一张exam表,表结构及测试数据如下:

    在这里插入图片描述

    • 要求查询出,科目编号为2的科目成绩及排名,代码如下:

      -- @rownum:记录当前行号当做排名
      select subid,score,@rownum:=@rownum+1 as rank
      from exam a,(select @rownum:=0) b
      where subid=2
      order by score desc
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 结果(rank排序)如下:

        在这里插入图片描述

  • 任务二、从查询结果中看到,排名3,4,5的同学分数是一样的,排名却不一样,分数一样的人,名次也要一样,修改代码如下:

    -- 如果成绩相等则排名也相等
    select subid,score,
    CASE
    -- 如果当前行的成绩等于上一行的,则直接显示行号
    	when @prev=score THEN @rownum
    -- 如果当前行的成绩不等于上一行的,则把当前行的成绩赋值给prev变量再让行号加1再显示
    	when @prev:=score THEN @rownum:=@rownum+1
    END as rank
    -- @prev存储上一行的成绩
    from exam a,(select @rownum:=0,@prev:=null) b
    where subid=2
    order by score desc
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 结果(rank排序):

      在这里插入图片描述

  • 任务三、要求当出现并列名次时,后面的同学进行跳跃排名,实现代码如下:

    select subid,score,
    -- if(@prev=score,@rownum,@inc):三元运算符 为true则选前面的结果,false为后面的结果
    -- @rownum:存储名次,@prev:上一行的分数,@inc:行号
    -- 如果当前分数和上一行分数相等,则取上一条数据的名次,如果不相等则取行号(行号一直在+1)
    @rownum:=if(@prev=score,@rownum,@inc) as rank, -- 1
    @inc:=@inc+1, -- 2
    @prev:=score -- 98 
    from exam a,(select @rownum:=0,@prev:=null,@inc:=1) b
    where subid=2
    order by score desc
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 结果(rank排序):

      在这里插入图片描述

12、分组统计

​ 在test数据库中有一张sx_target表,统计各个地区用户点击量,表结构及数据如下:

在这里插入图片描述

  • 任务一、查询每个城市每个月份的点击量,代码如下:

    SELECT city_name,state_month,sum(sx_sum)
    from sx_target
    GROUP BY city_name,state_month
    
    
    • 1
    • 2
    • 3
    • 4
    • 结果:

      在这里插入图片描述

  • 任务二、在现有统计基础上,显示每个城市的合计,以及所有城市的合计,代码如下:

    SELECT city_name,state_month,sum(sx_sum)
    from sx_target
    -- with rollup用来在分组的基础上再进行统计,例如group by a,b with rollup,先根据(a,b)统计,然后根据(a)统计,然后根据(null)统计。
    GROUP BY city_name,state_month with rollup
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 结果:

      在这里插入图片描述

八、MySQL管理

​ 一些初期从事开发工作的同学会经常问一些莫名奇妙的问题,比如在公司能连数据库,回家就连不上了;能查到数据,但是修改总是失败,面对数据库优化一无所知等等,瞬间就暴漏了自己的水平。本章内容主要是对数据库的管理和维护,这部分内容有助于开发人员快速适应企业级开发时应用的数据库环境,更有助于运维、实施人员快速掌握工作必备技能。

1、MySQL访问控制系统

​ MySQL实现了复杂的访问控制和权限系统,允许您创建用于处理客户端操作的全面的访问规则,并有效防止未经授权的客户端访问数据库系统。

当客户端连接到服务器时,MySQL访问控制有两个阶段:

  • 连接验证:连接到MySQL数据库服务器的客户端需要有一个有效的用户名和密码。此外,客户端连接的主机必须与MySQL授权表中的主机相匹配。默认情况下,mysql授权的主机只有本机,只允许本地连接,远程连接需要修改授权列表。
  • 请求验证:当连接成功建立后,对于客户端发出的每个语句,MySQL会检查客户端是否具有足够的权限来执行该特定语句。MySQL能够检查数据库,表和字段级别的权限。

2、用户账户管理

2.1、查看用户

​ MySQL在安装时会自动创建一个名为mysql的数据库,mysql数据库中有一张user表,包含用户账户及权限信息。

  • 任务一、查询数据库当前用户信息,代码如下:

    -- 使用mysql数据库
    use mysql
    select * from user
    
    
    • 1
    • 2
    • 3
    • 4
    • 结果:

      在这里插入图片描述

2.2、创建用户
  • 任务一、创建一个新用户dbadmin,密码为pwd123,代码如下所示:

    -- 创建用户
    -- identified by用于指定密码,@localhost表示该用户只能从本机登录,如果希望用户可以从任意主机连接,可以使用@’%’。
    create user dbadmin@localhost identified by 'pwd123'
    select * from user
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 结果:

      在这里插入图片描述

  • 任务二、查看新用户dbadmin的权限,代码如下所示:

    -- 查看新用户dbadmin的权限
    show grants for dbadmin@localhost
    
    
    • 1
    • 2
    • 3
    • 结果:

      在这里插入图片描述

      • 语法解析:上面结果中的usage表示dbadmin用户帐户只能登录到数据库服务器,没有其他权限。星.星中,前面的星号表示数据库,后面的星号表示表,星.星表示所有数据库中的所有表。
2.3、用户授权

​ 要授予用户权限,您可以使用GRANT语句。

  • 任务一、给新用户dbadmin授予超级管理员权限,代码如下所示:

    -- 给用户授予超级管理员权限
    -- with grant option:给予了给别人授权的权限
    grant all on *.* to dbadmin@localhost with grant option
    -- 查看权限
    show grants for dbadmin@localhost
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 结果:

      在这里插入图片描述

    • 查询结果表明dbadmin用户拥有所有数据库的所有表的权限,with grant option表示允许用户向其他用户授权,这样dbadmin就拥有了超级管理员权限。

  • 授予用户部分权限,代码如下:

    -- 创建用户lalala
    create user lalala IDENTIFIED by 'lalala'
    -- 授予对mysql1库的查询,修改,删除权限
    GRANT select,update,delete on mysql1.* to lalala
    
    • 1
    • 2
    • 3
    • 4
    • 上面代码表示创建一个lalala账号,密码为lalala,该用户只能操作mysql1数据库中的表,并且只能查询、修改和删除表中的数据,如果用lalala用户在mysql1数据库创建表就会报错。
  • 任务二、使用lalala用户连接mysql,然后在mysql1数据库中新建一张emp表,观察是否能够成功。

    create table emp(
    	id int PRIMARY key,
    	name varchar(20)
    )
    
    • 1
    • 2
    • 3
    • 4
    • 结果:

      在这里插入图片描述

    • 从运行结果可以看到,lalala的权限不能够在mysql1数据库创建表,但是可以查询,修改和删除

2.4、撤销权限

​ 在MySQL数据库中,可以使用REVOKE撤销用户权限。

  • 任务一、撤销lalala用户的select权限,代码如下:

    -- 撤销查询权限
    revoke select on mysql1.* from lalala;
    -- 查看权限
    show grants for lalala
    
    • 1
    • 2
    • 3
    • 4
    • 结果:

      在这里插入图片描述

    • 从结果可以看到,lalala已经不具备查询权限了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/567800
推荐阅读
相关标签
  

闽ICP备14008679号