第六十二篇 视图、触发器、事务、存储过程

一、视图(view)

1.视图相关概念

1.1 什么是视图

视图是由一张表或多张表的查询结果构成的一张虚拟表(把它当成查询缓存即可)

1.2 为甚么要用视图

1.缓存数据,简化多表SQL语句,避免多次编写SQL语句

2.隔离数据,以不同的视图来开放不同的数据给不同的访问对象(例如同一张工资表,老板可以查看全部信息,部门主管通过视图可以看到所属部门的全部信息,员工通过另一个视图只能看到自己的记录)

2.视图使用方法

注意:当我们修改视图里的数据是,也会修改原表数据

# 1.视图语法:
create or replace view(自定义字段,可写可不写) 视图名称 as SQL语句;
# 2.注意:
# create后面加 or replace 代表如果本视图存在则覆盖
# 我们在写的过程中可以根据情况可写可不写 or replace
# SQL语句必须是查询类SQL,SQL内容可以是查询视图,也就是说MySQL允许视图嵌套

# 3.示例:
create or replace view v_view as select r.* from tb_role r right join tb_admin a on a.id = r.admin_id;
# 视图更新有很多限制,比如,带常量的查询、带limit的查询、带聚合函数的查询、子查询等待,实际使用中自行检测
# 1.增
create or replace view v_view as select r.* from tb_role r right join tb_admin a on a.id = r.admin_id;

# 2.删
drop view v_view;

# 3.改
# 3.1 更改视图内数据
update v_view set password = 123 where id = 2; # 会把原表中的数据也一并更新
# 3.2 修改视图创建语句
alter view v_view as SQL语句

# 4.查
# 4.1查看数据
select * from v_view; 

# 4.2查看创建语句
show create view v_view;

# 4.3查看数据结构
desc v_view; 

3.实例

# 简化多表sql语句:有一个学生表和一个详细信息表 每次都要写查询连接查询比较繁琐,可以使用视图来简化查询

create table student(
  s_id int(3),
  name varchar(20),
  math float,
  chinese float 
);
insert into student values(1,'tom',80,70),(2,'jack',80,80),(3,'rose',60,75);

create table stu_info(
  s_id int(3),
  class varchar(50),
  addr varchar(100)
);
insert into stu_info values(1,'二班','安徽'),(2,'二班','湖南'),(3,'三班','黑龙江');

# 创建视图包含:编号、学生的姓名和班级
create view stu_v (编号,姓名,班级) as 
select student.s_id,student.name ,stu_info.class
from student,stu_info 
where student.s_id=stu_info.s_id;  # 内链接查询

# 查看视图中的数据
select *from stu_v;
# 隔离数据:一些情况下我们可能需要对某用户开放部分数据,隐藏其他数据,可以用视图来实现,下例中:希望市场部的员工只能看市场部的工资信息

# 创建工资表
create table salarys(
	id int primary key,
	name char(10),
	salary double,
	dept char(10)
);

insert into salarys values
	(1,"刘强东",900000,"市场"),
	(2,"马云",800090,"市场"),
	(3,"李彦宏",989090,"财务"),
	(4,"马化腾",87879999,"财务");

# 创建市场部视图
create view dept_sc as select *from salarys where dept = "市场";

# 查看市场部视图
select *from dept_sc;

4.总结及注意事项:

1.对视图数据的insert/update/delete会同步到原表中,但由于视图可能只含有原表的部分字段,因此可能会失败

2.MySQL可以分担程序中的部分逻辑,但这样一样也会给后续的维护造成一些影响,比如维护更麻烦

3.如果修改了表结构,那么视图势必也会随之修改,因此没有直接在程序中修改SQL方便

二、触发器(trigger)

1.什么是触发器

监视某种情况,并触发某种操作,它是提供给程序员保证数据完整性的一种方法,它是与表事件相关的特殊的存储过程,它的执行不是由程序调用,也不是手工启动,而是由事件来触发,比如对一个表进行操作(insert、delete、update)时就会激活它执行

2.为什么使用触发器

1.当我们想要在一个表的记录被更新时做一些操作时就可以使用触发器
2.但我们其实可以用python做这些事情,因为python的扩展性更强,语法更简单

3.如何使用触发器

1.触发器创建语法的四要素:

  • 1.1 监视地点(table)
  • 1.2 监视事件(insert/update/delete)
  • 1.3 触发时间(after/before)
  • 1.4 触发事件(insert/update/delete)

2.基本语法

create trigger trigger_name trigger_time trigger_event on table_name for each row
begin
	SQL语句;
end;
  • 2.1 对应关系
# 1.trigger_time:支持的时间点,发生前(before)、发生后(after)

# 2.trigger_event:支持的时间,update/insert/delete

# 3.在触发器中可以访问到将要被修改的那一行数据,根据事件的不同,可访问的数据也不同
## 3.1 update 可通过old访问旧数据,也能通过new访问新数据
## 3.2 insert 仅可通过new访问新数据
## 3.3 delete 仅可通过old访问旧数据

# 4.可以将new和old看作一个对象,其封装了记录中的所有字段

**3.实例:

# 有cmd表和错误日志表,需求:在cmd执行失败时自动将信息存储到错误日志表中

# 1.创建cmd表
CREATE TABLE cmd (
    id INT PRIMARY KEY auto_increment,
    USER CHAR (32),
    priv CHAR (10),
    cmd CHAR (64),
    sub_time datetime, #提交时间
    success enum ('yes', 'no') #0代表执行失败
);

# 2.创建错误日志表
CREATE TABLE errlog (
    id INT PRIMARY KEY auto_increment,
    err_cmd CHAR (64),
    err_time datetime
);

# 3.创建触发器
'''
3.1 delimiter:用于修改默认的行结束符(分界符),也即是将分号换成其他符号作为结束符。由于在触发器中有多条SQL语句,它们需要使用分号来结束,但是触发器是一个整体,所以我们需要先更换默认的结束符,在触发器编写完后再将结束符设置回分号,也就是说在修改了的行结束符中的SQL语句,无论有多少个分号都不会立即执行,而是会直到出现修改后的行结束符才会执行,需要注意的是我们修改行结束符时,要将delimiter与行结束符隔开,不能连着。只要是里面包含多行SQL语句时,都需要用到delimiter来帮忙实现(比如,触发器、存储过程)

3.2 在mysql中输入分号回车,mysql会立即将语句发送给服务器端,修改行结束符仅仅是告诉mysql客户端,语句没有写完,不要立即发送
'''
delimiter //   # 可以用任意特殊符号替换分号
create trigger trigger1 after insert on cmd for each row
begin
if new.success = "no" then
    insert into errlog values(null,new.cmd,new.sub_time);
/;
end//    # 可以用 | 代替 //
delimiter ;   # 注意:这里delimiter和分号需要一个空格隔开(表示将分号换为行结束符)

#往表cmd中插入记录,触发触发器,根据IF的条件决定是否插入错误日志
INSERT INTO cmd (
    USER,
    priv,
    cmd,
    sub_time,
    success
)
VALUES
    ('egon','0755','ls -l /etc',NOW(),'yes'),
    ('egon','0755','cat/etc/passwd',NOW(),'no'),
    ('egon','0755','useradd xxx',NOW(),'no'),
    ('egon','0755','ps aux',NOW(),'yes');

# 查看错误日志表中的记录是否有自动插入
select *from errlog;

4.删除触发器

# 语法:
drop trigger trigger_name;
# 案例:
drop trigger trigger1;

# 题外话:
# 同样的这种需求我们完全可以在python中来完成! mysql最想完成的事情是将所有能处理的逻辑全部放到mysql中,那样一来应用程序开发者的活儿就变少了,相应的数据库管理员的工资就高了,可惜大多中小公司都没有专门的DBA

5.注意

  • 1.外键不能触发事件,主表删除了某个主键,从表也会相应删除,但是并不会执行触发器,触发器中不能使用事务
  • 2.相同时间点的相同事件的触发器,不能同时存在

三、事务(transaction)

1.什么是事务

1.事务是逻辑上的一组操作,一个最小的不可再分的工作单元,通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)

2.银行转账业务包括:从原有账户减去转账金额、给目标账户加上转账金额

2.事务的相关特性

1.一般来说,事务必须满足4个条件:

  • 1.原子性(不可分割性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样
  • 2.一致性:在事务开始之前和事务结束之后,数据库的完整性没有被破坏。也就是写入的数据(资料)必须完全符合所有的预设规则(这包含资料的精确度、串联性以及后续数据可以自发性的完成预定的工作)
  • 3.隔离性:数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)
  • 4.持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

2.修改隔离级别

  • 1.事务的用户隔离级别
# 事务的用户隔离级别:
# 数据库使用者可以控制数据库工作在哪个级别下,就可与防止不同的隔离性问题

read uncommitted---不做任何隔离,可能脏读,幻读
- 事物A和事物B,事物A未提交的数据,事物B可以读取到
- 这里读取到的数据叫做“脏数据”
- 这种隔离级别最低,这种级别一般是在理论上存在,数据库隔离级别一般都高于该级别

read committed---可以防止脏读,不能防止不可重复读,和幻读,
- 事物A和事物B,事物A提交的数据,事物B才能读取到
- 这种隔离级别高于读未提交
- 换句话说,对方事物提交之后的数据,我当前事物才能读取到
- 这种级别可以避免“脏数据”
- 这种隔离级别会导致“不可重复读取”
- Oracle默认隔离级别

Repeatable read---可以防止脏读,可重复读,不能防止幻读
- 事务A和事务B,事务A提交之后的数据,事务B读取不到
- 事务B是可重复读取数据
- 这种隔离级别高于读已提交
- 换句话说,对方提交之后的数据,我还是读取不到
- 这种隔离级别可以避免“不可重复读取”,达到可重复读取
- 比如1点和2点读到数据是同一个
- MySQL默认级别
- 虽然可以达到可重复读取,但是会导致“幻像读”

-----------------------------------------------------------
参考了原作者:浅然_ 
来源:CSDN 
原文:https://blog.csdn.net/w_linux/article/details/79666086 
-----------------------------------------------------------

Serializable---数据库运行在串行化实现,所有问题都没有,就是性能低

# 幻读:别人在执行insert/delete,你正在读数据
# 不可重复读:别人在执行update,你不能读数据
  • 2.修改用户的隔离级别
# 1.查询当前隔离级别
select @@tx_isolation;

# 2.修改全局的隔离级别
set global transaction isolation level read committed;   # 修改为读提交

# 3.另一种修改全局的隔离级别
set @@tx_isolation = 'Repeatable read'   # 修改为可重复读

# 4.修改局部的隔离级别
set session transaction isolation level Serializable;  # 修改为串行化

补充

@@ :表示系统内置变量

@ :表示用户自定义的变量

3.为什么需要事务

保证数据安全,维护数据库的完整性

4.如何使用事务

1.事务只和DML(insert、update、delete)语句有关,或者说DML语句才有事务

2.语法

# 开启事务 
start transaction 

sql语句  # 根据业务需求可以有许多行SQL语句

# rollback  # 回滚:即撤销没有提交之前的所有操作 

commit   # 提交:一旦提交就持久化了

3.实例

# 创建表
CREATE TABLE `account` (
  `name` char(10),
  `money` float  
);

# 插入数据
insert into account values('king', '1000'), ('jojo', '0');

# 使用事务(要么全部执行,要么都不执行,保证资金安全)
start transaction;
update account set money = money - 100 where name = "king";
update account set money = money + 100 where name = "jojo";
commit;

4.使用事务:

# 1.开启
start transaction; --开启事物,在这条语句之后的sql将处在同一事务,并不会立即修改数据库

# 2.提交
commit; --提交事务,让这个事物中的sql立即执行数据的操作,

# 3.回滚
rollback; --回滚事务,取消这个事物,这个事物不会对数据库中的数据产生任何影响

# 4.保存点
savepoint; --在数据库事务处理中实现“子事务(subtransaction)”,也称嵌套事务的方法。事务可以回滚到savepoint 而不影响savepoint 创建前的变化,不需要放弃整个事务

# 4.1 删除savepoint
# 4.1.1 保留点在事务处理完成(执行一条 ROLLBACK 或 COMMIT)后自动释放
# 4.1.2 删除指定保留点
release savepoint savepoint_name;

5.提交、回滚、保存点:

# 1.当一个事务执行过程中出现了异常时,应该回滚

# 2.当事务中所有语句都执行成功时,应该提交事务

# 3.可以通过rollback指定回滚到某一个savepoint

# 实例:
start transaction;
update account set money = money - 100 where name = "king";
savepoint a;
update account set money = money - 100 where name = "king";
savepoint b;
update account set money = money - 100 where name = "king";
savepoint c;

# 回滚到保存点a,则savepoint a;之前的那一行不会执行
rollback to a;

6.注意:

  • 事务的回滚的前提是能捕捉到异常,否则无法决定何时回滚,mysql中需要使用存储过程才能捕获异常,Python中很简单就实现了
# python中使用事务案例

try:
    conn = pymysql.connect(host="127.0.0.1",user="root",password="",db="day46")
    print("连接服务器成功!")

    cursor = conn.cursor(pymysql.cursors.DictCursor)
    sql = 'update account set money = money - 1000 where id = 1;'
    sql2 = 'update account set money = money + 1000 where id = 2;'  # money打错了将导致执行失败
    try:
        cursor.execute(sql)
        cursor.execute(sql2)
        conn.commit()
        print("执行成功 提交")
    except:
        print("发送错误   回滚..")
        conn.rollback()

except Exception as e:
    print("连接服务器失败.....")
    print(type(e),e)
finally:
    if cursor:cursor.close()
    if conn:conn.close()

四、存储过程

1.什么是存储过程

1.一种在数据库中存储复杂程序,以便外部程序调用的数据库对象
2.存储过程是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可以通过指定存储过程的名字并给定参数(需要时)来调用执行,与python中的函数类似
3.存储过程思想上很简单,就是数据库SQL语言层面的代码封装与重用

2.为什么需要存储过程

1.存储过程中可以包含任何的SQL语句(视图、事务、流程控制等等),可以替代应用程序完成数据相关的逻辑处理

2.通过存储过程可以将复杂操作封装在容易使用的单元中,简化了操作,隔离了复杂度

3.由于不要求反复建立一系列处理步骤,保证了数据的完整性

4.因为使用存储过程比使用单条SQL语句要快,提高了效率

5.详解:

# 1.应用程序仅负责业务逻辑编写,所有与数据相关的逻辑都交给mysql来完成,通过存储过程(推荐使用)

# 1.1 优点:
# 1.1.1 应用程序与数据处理完全耦合,一堆复杂的SQL被封装成了一个简单的存储过程,考虑到网络环境因素,效率高
# 1.1.2 应用程序开发者不需要编写SQL语句,开发效率高

# 1.2 缺点:
# 1.2.1 python语法与mysql语法区别巨大,学习成本高
# 1.2.2 并且各种数据库的语法大不相同,所以移植性非常差
# 1.2.3 应用程序开发者与BDA的跨部门沟通成本高,造成整体效率低


# 2.应用程序不仅编写业务逻辑,还需要编写所有的sql语句

# 2.1 优点:扩展性高,对于应用程序开发者而言,扩展性和维护性相较于第一种都有所提高

# 2.2 缺点:sql语句过于复杂,导致开发效率低,且需要考虑sql'优化问题


# 3.应用程序仅负责业务逻辑,sql语句的编写交给ORM框架(常用解决方案)

# 3.1 优点:应用程序开发者不需要编写sql语句,开发效率高

# 3.2 缺点:执行效率低,由于需要将对象的操作转化为sql语句,且需要通过网络发送大量sql

3.如何使用存储过程

1.相关概念:

in 表示输入参数

out 表示输出参数
# 注意:存储过程的out类参数必须是一个变量,不能是值

inout 表示既能输入又能输出

parameter_type 参数类型(输入输出既可以输入又可以输出)

parameter_name 参数名称

data_type 参数类型:可以是mysql支持的数据类型

2语法:

create procedure procedure_name(parameter_type parameter_name data_type)
begin
sql语句 + 流程控制
end

3.实例:

# 使用存储过程完成对student表的查询

delimiter |
create procedure p1(in m int, in n int, out res int)
begin
	select * from student where chinese > m and chinese < n;
	# select * from student where chineseeee > m and chinese < n; # 测试执行失败的情况
	set res = 100; # 改变变量的值
end |
delimiter ;

set @res = 0;  # 自定义变量

# 调用存储过程
call p1(1, 2, @res);

# 查看执行结果
select @res;

4.在python中调用存储过程

import  pymysql
#建立连接
conn = pymysql.connect(
    host="127.0.0.1",
    user="root",
    password="admin",
    database="db02"
)
# 获取游标
cursor = conn.cursor(pymysql.cursors.DictCursor)

# 调用用存储过程
cursor.callproc("p1",(70,80,0)) #p1为存储过程名 会自动为为每个值设置变量,名称为 @_p1_0,@_p1_1,@_p1_2
# 提取执行结果时,是否有结果取决于存储过程中的sql语句
print(cursor.fetchall())
# 获取执行状态
cursor.execute("select @_p1_2")
print(cursor.fetchone())

# 此处pymysql会自动将参数都设置一个变量所以可以直接传入一个值,当然值如果作为输出参数的话,传入什么都无所谓

5.存储过程的增删改查

# 增
delimiter    # 修改行结束符
create procedure procedure_name(parameter_type parameter_name data_type)
begin
sql语句集与流程控制
end 
delimiter ;  # 把分号再变回行结束符


# 删
drop procedure 存储过程名称;

# 改
修改存储过程的意义不大,不如删除重写

# 查
# 查看当前库所有存储过程名称
select name from mysql.proc where db = 库名 and type = 'PROCEDURE';

# 查看创建语句
show create procedure 存储过程名称;

6.存储过程中的事务应用

  • 抛开沟通成本,学习成本,存储过程无疑是效率最高的处理方式,面试会问,一些公司也有一些现存的存储过程,重点掌握
# 存储过程中支持任何的SQL语句,包括事务

# 模拟转账中发生异常,进行回滚
delimiter //
create PROCEDURE p5(OUT p_return_code tinyint)
BEGIN 
	
	# 声明:对于异常,退出处理程序
	# 发生异常,就回滚
    DECLARE exit handler for sqlexception  
    BEGIN 
        # ERROR    
        set p_return_code = 1; 
        rollback; 
    END; 
    
    # 发生警告,就回滚
    # exit 也可以换成continue 表示发生异常时继续执行
    DECLARE exit handler for sqlwarning 
    BEGIN 
        # WARNING 
        set p_return_code = 2; 
        rollback; 
    END; 

	# 开启事务
    START TRANSACTION; 
    update account set money = money - 1000 where id = 1;
    update account set moneys = money - 1000 where id = 1; # moneys字段导致异常
    # 提交事务
    COMMIT; 

    # SUCCESS 
    set p_return_code = 0; #0代表执行成功
END //
delimiter ;

#在mysql中调用存储过程
set @res=123;  # 自定义变量

call p5(@res);   # 用call调用存储过程

select @res;     # 查看自定义变量的值

五、函数

1.时间相关

2.字符串相关

3.数字相关

4.其他函数

5.使用说明

1.一般使用查询语句进行使用
2.大写的都是关键字,不要修改,其他小写的一般是输入参数的类型

# 比如
select date_add(now(),interval 1 year);  # 表示在当前的时间上加上一年得到的时间

6.自定义函数

1.说明

1.paramters 只能是in 输入参数 参数名 类型

2.必须有返回值

3.不能加begin 和end

4.returns 后面是返回值的类型 这里不加分号

5.return 后面是要返回的值

6.实例:

# 两数相加

create function addfuntion(a int,b int)

returns int return a + b;

# 执行函数
select addfuntion(1,1);  # 和上述的函数一样,都是用select来联用

7.SQL函数的增删查改

# 1.查看创建语句 
show create function 函数名;

# 2.查看所有函数的状态
show function status;

# 3.查看某个库下所有函数
select name from mysql.proc where db = "库名称" and type = "FUNCTION";

# 4.删除
drop function 函数名;

六、数据备份与恢复

1.使用mysqldump程序进行备份

# mysqldump.exe 本质上是一个程序,因此要在cmd中直接允许

# 1.备份多个表(先指定库,后面全是表名)
mysqldump -u用户名 -p密码  库名 表名1 表名2 ...... > 文件路径(fileName.sql)

# 2.备份多个数据库
mysqldump -uroot -p111 --databases 库名 库名  > x3x.sql
#指定 --databases 后导出的文件包含 创建库的语句   而上面的方式不包含

# 3.备份所有库
mysqldump -uroot -p111 --all-databases > all.sql

# 4.恢复数据:
# 4.1没有登录mysql 
mysql < 文件的路径

# 4.2已经登录了MySQL 
source  文件路径

# 5.注意: 如果导出的sql中没有包含选择数据库的语句 需要手动加上 

# 6.Linux中自动备份指令
linux crontab 指令可以定时执行某一个指令

七、流程控制

# 1.if判断
# 1.1 if语句
if 条件 then
语句;
end if;

# 1.2 if elseif
if 条件 then
语句1;
elseif 条件 then
语句2;
else 语句3;
end if;


# 2.定义变量
declare 变量名 类型 default 值;
例如: declare i int default 0;


# 3.CASE 语句
create procedure caseTest(in type int)
begin
CASE type 
when 1  then select "type = 1";
when 2  then select "type = 2";
else select "type = other";
end case;
end


# 4.WHILE循环
循环输出10次hello mysql
create procedure showHello()
begin 
declare i int default 0;
while  i < 10 do
select "hello mysql";
set i  = i + 1;
end while;
end


# 5.LOOP循环的
输出十次hello mysql;
create procedure showloop()
begin 
declare i int default 0;
aloop: LOOP
select "hello loop";
set i = i + 1;
if i > 9 then leave aloop;
end if;
end LOOP aloop;
end


# 6.REPEAT循环
#类似do while
#输出10次hello repeat
create procedure showRepeat()
begin
declare i int default 0;
repeat
select "hello repeat";
set i = i + 1;
until i > 9
end repeat;
end

#输出0-100之间的奇数
create procedure showjishu()
begin
declare i int default 0;
aloop: loop
set i = i + 1;
if i >= 101 then leave aloop; end if;
if i % 2 = 0 then iterate aloop; end if;
select i;
end loop aloop;
end

八、正则匹配

语法:
select  *from  table where 字段名  regexp "表达式!";

create table info(name char(20));
insert into  info values("jack sbaro"),("jack rose"),("jerry sbaro"),("sbaro jerry"),("jerry");

# 注意: 不能使用类似 w 这样的符号   需要找其他符号来代替  
原文地址:https://www.cnblogs.com/itboy-newking/p/11276846.html