Fork me on GitHub

视图、触发器、事务、存储过程

一 视图

视图是一个虚拟表(非真实存在),其本质是根据sql语句获取动态的数据集,并为其命名,
用户使用时只需要使用名称即可获得结果集,可以将该结果集当做表来使用。

使用视图我们可以把查询过程中的临时表摘出来,用视图去实现,这样以后再想操作该临时表的数据时就无需重写复杂的sql了,直接去视图中查找即可,但视图有明显地效率问题,并且视图是存放在数据库中的,如果我们程序中使用的sql过分依赖数据库中的视图,即强耦合,那就意味着扩展sql极为不便,因此并不推荐使用。

mysql> select * from course;
+-----+--------+------------+
| cid | cname  | teacher_id |
+-----+--------+------------+
|   1 | 生物   |          1 |
|   2 | 物理   |          2 |
|   3 | 体育   |          3 |
|   4 | 美术   |          2 |
+-----+--------+------------+

mysql> select * from teacher;
+-----+-----------------+
| tid | tname           |
+-----+-----------------+
|   1 | 张磊老师          |
|   2 | 李平老师          |
|   3 | 刘辉老师          |
|   4 | 朱云海老师        |
+-----+-----------------+

#查询李平老师的课程名

select cname from course where 
teacher_id=(select tid from teacher where tname='李平老师' )
+--------+
| cname  |
+--------+
| 物理   |
| 美术   |
+--------+

#子查询出临时表,作为teacher_id等判断依据
select tid from teacher where tname='李平老师'


1 创建视图

#语法: create view 视图名称 as sql语句
create view teacher_view as select tid from teacher where tname='李平老师';

#于是查询李平老师教授的课程名的sql可以改写为
mysql> select cname from course where teacher_id = (select tid from teacher_view);

+--------+
| cname  |
+--------+
| 物理   |
| 美术   |
+--------+

#注意
1.使用视图以后就无需每次都重写子查询的sql,但是这么做效率不高,还不如我们写子查询的效率高

2.而且一个致命的问题:视图是存放到数据库中的,如果我们程序中的sql过分依赖,一旦sql需要修改且涉及到视图的部分,则必须去数据库中进行修改,而通常在公司中数据库有专门的DBA负责,你要想完成修改,必须付出大量的沟通成本DBA可能才会帮你完成修改,极其地不方便。

2 使用视图

mysql>CREATE VIEW cou2tea as SELECT course.*,teacher.tname FROM course 
INNER JOIN teacher on course.teacher_id=teacher.tid;

mysql> select * from cou2tea;
+-----+--------+------------+--------------+
| cid | cname  | teacher_id | tname        |
+-----+--------+------------+--------------+
|   1 | 生物   |          1 | 张磊老师     |
|   2 | 物理   |          2 | 李平老师     |
|   3 | 体育   |          3 | 刘辉老师     |
|   4 | 美术   |          2 | 李平老师     |
+-----+--------+------------+--------------+

mysql> update cou2tea set teacher_id=4 where cid=1; 
+-----+--------+------------+-----------------+
| cid | cname  | teacher_id | tname           |  #视图发生改变
+-----+--------+------------+-----------------+
|   1 | 生物   |          4 | 朱云海老师      |
|   2 | 物理   |          2 | 李平老师        |
|   3 | 体育   |          3 | 刘辉老师        |
|   4 | 美术   |          2 | 李平老师        |
+-----+--------+------------+-----------------+

mysql> select * from course;
+-----+--------+------------+
| cid | cname  | teacher_id |   #原始表也同时发生改变
+-----+--------+------------+
|   1 | 生物   |          4 |
|   2 | 物理   |          2 |
|   3 | 体育   |          3 |
|   4 | 美术   |          2 |
+-----+--------+------------+

#更新视图中的数据也将同时更新原始表中的记录,准确来说是直接改动原始表中的记录,
#在mysql中视图存储的只有整个表的结构没有数据,之所有有完整数据出来,是因为映射了相应原始表的数据。

#删除同理

注意:
一般创建视图多数只为查询,如果更改视图的内容,则影响了背后多个原始表的数据内容,
所以在涉及多个表的情况下是根本无法修改视图中的记录的。

3 删除视图

语法:DROP VIEW 视图名称

DROP VIEW teacher_view

二 触发器

使用触发器可以定制用户对表进行【增、删、改】操作时前后的行为,注意:没有查询

1 创建触发器

# 插入前
CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROW
BEGIN
    ...
END

# 插入后
CREATE TRIGGER tri_after_insert_tb1 AFTER INSERT ON tb1 FOR EACH ROW
BEGIN
    ...
END

# 删除前
CREATE TRIGGER tri_before_delete_tb1 BEFORE DELETE ON tb1 FOR EACH ROW
BEGIN
    ...
END

# 删除后
CREATE TRIGGER tri_after_delete_tb1 AFTER DELETE ON tb1 FOR EACH ROW
BEGIN
    ...
END

# 更新前
CREATE TRIGGER tri_before_update_tb1 BEFORE UPDATE ON tb1 FOR EACH ROW
BEGIN
    ...
END

# 更新后
CREATE TRIGGER tri_after_update_tb1 AFTER UPDATE ON tb1 FOR EACH ROW
BEGIN
    ...
END
#准备表
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代表执行失败
);

CREATE TABLE errlog (
    id INT PRIMARY KEY auto_increment,
    err_cmd CHAR (64),
    err_time datetime
);





#创建触发器
delimiter // #表示以//为结束符
CREATE TRIGGER tri_after_insert_cmd AFTER INSERT on cmd for EACH ROW
BEGIN
		IF new.success = 'no' then #如果信号为失败,则在错误日志表中写入错误命令与时间。
						INSERT INTO errlog(err_cmd,err_time) VALUES(new.cmd,new.sub_time);
			END IF;
END//
delimiter;

#往表cmd中插入记录,触发触发器,根据IF的条件决定是否插入错误日志
mysql> SELECT * FROM errlog;
+----+-----------------+---------------------+
| id | err_cmd         | err_time            |
+----+-----------------+---------------------+
|  1 | cat /etc/passwd | 2020-09-09 16:56:40 |
|  2 | useradd xxx     | 2020-09-09 16:56:40 |
+----+-----------------+---------------------+

#结果有两条错误日志写入

2 使用触发器

触发器无法由用户直接调用,而是由于对表的【增/删/改】操作被动引发的。

3 删除触发器

drop trigger tri_after_insert_cmd;

三 事务

1 什么是事务?

数据库事务是指作为单个逻辑工作单元执行的一系列操作(SQL语句)。
这些操作要么全部执行,要么全部不执行。

2 为什么需要事务?

经典的银行转账行为,A账户转给B账户100元,数据库操作需要两步,第一步A账户减100元,
第二步B账户加100元,如果没有事务并且两步中间发生异常,那么A账户已经发出减100元指令,
然后中间异常,而B账户没有接收到要加100元的指令,那么A账户少了100元而B账户没有加钱,
所以需要这两步的操作统一,即执行成功A减钱B加钱,执行不成功,则A账户的钱没有动或是减了
之后迅速得到返回。

3 事务特性 (4种)

  • 原子性:强调事务的不可分割
  • 一致性:事务的执行的前后数据的完整性保持一致
  • 隔离性:一个事务执行的过程中,不应该受到其他事务的打扰
  • 持久性:事务一旦结束,数据就持久到数据库

4 事务运行模式 (3种)

​ 1.自动提交事务:默认事务管理模式。如果一个语句成功地完成,则提交该语句;
​ 如果遇到错误回滚该语句(返回成原来的数据)。
​ 2.显式事务:以begin transaction显示开始,以commit或rollback显示结束。
​ 3.隐式事务:当连接以此模式进行操作时,sql将在提交或回滚当前事务后自动启动新事务,
​ 无序描述事务的开始,只需提交或回滚每个事务,它生成连续的食物链。

create table userinfo(
id int primary key auto_increment,
name char(32),
balance int
);

INSERT into userinfo(name,balance) 
values
('买家',1000),
('第三方',1000),
('卖家',1000);

#原子操作
START TRANSACTION; #声明以下sql语句为一个业务不可分割
UPDATE userinfo set balance=800 where name='买家'; #买家扣200元购买商品
UPDATE userinfo set balance=1100 where name='第三方';#第三方收取100元手续费	
UPDATE userinfo set balance=1100 where name='卖家'; #卖家最后得款100元
commit;   #提交此业务
+----+-----------+---------+
| id | name      | balance |
+----+-----------+---------+
|  1 | 买家      |     800 |
|  2 | 第三方    |    1100 |
|  3 | 卖家      |    1100 |
+----+-----------+---------+

#出现异常,回归到初始状态
START TRANSACTION; #声明以下sql语句为一个业务
UPDATE userinfo set balance=900 where name='买家'; #买家扣200元购买商品
UPDATE userinfo set balance=1100 where name='第三方';#第三方收取100元手续费	
UPDATE userinfo set balance=1100 where name='卖家'; #卖家最后得款100元
ROLLBACK;
commit;   #提交此业务
+----+-----------+---------+
| id | name      | balance |
+----+-----------+---------+
|  1 | 买家      |    1000 |
|  2 | 第三方    |    1000 |
|  3 | 卖家      |    1000 |
+----+-----------+---------+

利用python捕捉异常后进行事务回滚,没有异常则直接提交事务

import pymysql

conn = pymysql.connect(host='hostlocal', user='root', password='giaogiao', database='db')

cursor = conn.cursor()
sql_1 = 'UPDATE userinfo set balance=900 where name="买家";'
sql_2 = 'UPDATE userinfo set balance=1100 where name="第三方";'
sql_3 = 'UPDATE userinfo set balance=1100 where name="卖家"; '

try:
    cursor.execute(sql_1)
    cursor.execute(sql_2)
    cursor.execute(sql_3)
except Exception as e:
    conn.rollback()  # 出现异常直接返回到原来的数据状态
    print('事务处理失败')

else:
    conn.commit()  # 没有异常直接提交到库中保存
    print('事务处理成功')

cursor.close()
conn.close()

总结:事务用于将某些操作的多个sql作为原子性操作,一旦有某一个出现错误,即可回滚到原来的状态,从而保证数据库数据完整性。

四 存储过程

1 介绍

存储过程包含了一系列可执行的sql语句,存储过程存放于mysql中,通过调用它的名字可以执行其内部的一对sql

使用存储过程的优点:

​ 1.用于替代程序写的sql语句,实现程序与sql的解耦合

​ 2.基于网络传输,传别名的数据量小,而直接传sql数据量大

使用存储过程的缺点:

​ 程序员扩展功能不方便

补充:程序与数据库结合使用的三种方式

#方式一:
	mysql:存储过程
	程序:调用存储过程
	(解耦合)
	
#方式二:
	mysql:
	程序:纯sql语句
	(耦合)
	
#方式三:
	mysql:
	程序:类和对象,即ORM(本质还是纯sql语句)

2 创建简单储存过程(无参)

#创建blog表
CREATE TABLE blog(
id int PRIMARY KEY auto_increment,name VARCHAR(10),sub_time datetime not null DEFAULT now());

#制造存储过程对象名为p
delimiter //
CREATE PROCEDURE p()
BEGIN
	SELECT * FROM blog;
	INSERT INTO blog(name,sub_time) values('xxx',now());
END //
delimiter;

#在mysql中通过p调用存储过程中的sql语句(里面sql语句全部执行一遍)
call p()

#在python中基于pymysql调用
import pymysql

conn = pymysql.connect(host='hostlocal', user='root', password='giaogiao', database='db')
cursor = conn.cursor()
cursor.callproc('p')
print(cursor.fetchall())

3 创建存储过程(有参)

对于存储过程,可以接收参数,其参数有三类:

#in    仅用于传入参数用
#out   仅用于返回值用
#inout 既可以传入又可以当做返回值
#in:传入参数

delimiter //
CREATE PROCEDURE p2(in n int)
BEGIN
	SELECT * FROM blog where id>n;
	
END //
delimiter;

#在mysql中调用
call p2(3)

#在python中基于pymysql调用
cursor.callproc('p2',(3,)) #必须以元组的形式传参
print(cursor.fetchall())

#out:返回值

delimiter //
CREATE PROCEDURE p3(
		in n int,
		out res INT
)
BEGIN
	SELECT * FROM blog where id>n;
	set res=1;
	
set @res=1; #0代表假,1代表真 
call p3(2,@res);
SELECT @res; #产生第二结果是@res的表

#在python中基于pymysql调用
cursor.callproc('p3', (3, 0))  # 0相当于set @res=0
print(cursor.fetchall())  # 查询select的结果
cursor.execute('select @_p3_0,@_p3_1;')  # @p3_0代表第一个参数,@p3_1代表第二个参数,即返回值
print(cursor.fetchall())

# ((3, 1),) 参数3为第一个参数,1是第二个参数设定的返回值
#inout:既可以传入又可以当作返回值

delimiter //
create procedure p4(
    inout n1 int    #此参数可以做为传入的参数,也是返回值
)
BEGIN
    select * from blog where id > n1;
    set n1 = 1;
END //
delimiter ;

#在mysql中调用
set @x=3;  #第一个参数赋值
call p4(@x);
select @x;


#在python中基于pymysql调用
cursor.callproc('p4',(3,)) #当作正常参数传入
print(cursor.fetchall()) #查询select的查询结果

cursor.execute('select @_p4_0;') #执行该语句得到返回值
print(cursor.fetchall())

4 执行存储过程

-- 无参数
call proc_name()

-- 有参数,全in
call proc_name(参数1,参数2)

-- 有参数,有in,out,inout
set @t1=0;
set @t2=3;
call proc_name(参数1,参数2,@t1,@t2)

5 删除存储过程

drop procedure proc_name;
posted @ 2020-10-07 18:14  artherwan  阅读(110)  评论(0编辑  收藏  举报