數據庫(七)


前言

本篇博客學習內容為視圖、觸發器、事務、存儲過程、函數、數據備份及流程控制。

視圖

什么是視圖?

視圖是由一張表或多張表的查詢結果構成的一張虛擬表,建立一張視圖后會在數據庫中保留一個以 frm 后綴結尾的文件,只保留了數據結果,所有的數據都來自 sql 語句。

為什么使用視圖?

在進行多表查詢的時候,sql 語句會非常非常長,比如

select t1.student_id from (select student_id,num from score where course_id = (select cid from course where cname = '物理')) as t1 join(select student_id,num from score where course_id = (select cid from course where cname = '生物')) as t2 on t1.student_id = t2.student_id where t1.num > t2.num;

看是不是很長,這還只是三表查詢,如果遇到更加復雜的表結構肯定會更長,不過那樣的話對表的維護困難也加大了。如果每次都編寫需要得到相同數據的 sql 語句會是一件很麻煩的事,可以把經常需要查詢的 sql 語句轉變為視圖就可以避免重復寫 sql 語句的問題。

視圖除了可以減少 sql 語句的編寫次數,還可以使用不同的視圖來展示不同數據的訪問,那么給某些用戶設置權限不就可以了嗎?注意,設置的權限要么只能看某張表的全部數據,要么只能看某張表中的某個 column 的數據,也就是列數據,列數據只是保存了字段名,比如說我要查看我的當月工資,是需要查看一行數據的,這樣權限就幫不了忙了。(當然可以加 where 條件,在這里是介紹視圖)

使用方法

創建視圖

mysql> create [or replace] view 視圖名 [(column_list)] as select_statement;

加上 or replace 時如果已經存在相同視圖則替換原有視圖,column_list 指定哪些字段要出現在視圖中。注意:由於是一張虛擬表,視圖中的數據實際來源於其他表,所以在視圖中的數據不會出現在硬盤上,也就是只會保存一份數據結構。

使用視圖

視圖是一張虛擬表,所以使用方式與普通表沒有區別。

查看視圖

  1. 查看數據結構
mysql> desc view_name;
  1. 查看創建語句
mysql> show create view view_name;

修改視圖

mysql> alter view_name select_statement;

刪除視圖

mysql> drop view view_name;

具體使用

案例一:簡化多表 sql 語句

# 准備數據
mysql> create database db02 charset utf8;
mysql> use db02;
mysql> create table student(
	   s_id int(3),
	   name varchar(20),
       math float,
       chinese float);
mysql> insert into student values(1,'tom',80,70),(2,'jack',80,80),(3,'rose',60,75);
mysql> create table stu_info(
	   s_id int(3),
       class varchar(50),
       addr varchar(100));
mysql> insert into stu_info values(1,'二班','安徽'),(2,'二班','湖南'),(3,'三班','黑龍江');
# 創建視圖包含編號、學生姓名、班級
mysql> 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;
# 查看視圖中的數據
mysql> select * from stu_v;

案例二:隔離數據

# 創建工資表
mysql> create table salarys(
	   id int primary key,
       name char(10),
       salary double,
       dept char(10));
mysql> insert into salarys values
	   (1,'劉強東',800000,'市場'),
	   (2,'馬雲',899990,'市場'),
	   (3,'李彥宏',989090,'市場'),
	   (4,'馬化騰',88889999,'財務');
# 創建市場部視圖
mysql> create view dept_sc as select * from salarys where dept = '市場';
mysql> select * from dept_sc;

注意:對視圖數據的 insert update delete 會同步到原表中,但由於視圖可能是部分字段,很多時候會失敗。

總結:mysql 可以分擔程序中的部分邏輯,但這樣一來后續的維護會變得更麻煩。如果需要改表結構,那意味着視圖也需要相應的修改,沒有直接在程序中修改 sql 來的方便。

觸發器

什么是觸發器?

觸發器是一段與表有關的 mysql 程序,當這個表在某個時間點發生了某種事件時,將會自動執行相應的觸發器程序。

何時使用觸發器

當我們想要在一個表記錄被更新時做一些操作時就可以說使用觸發器,但是完全可以在 python 中來完成這個事情。

創建觸發器

語法

mysql> create trigger t_name t_time t_event on table_name for each row
begin
stmts...
end

支持的時間點(t_time):事件發生之前和之后 before|after

支持的事件(t_event):update、insert、delete

在觸發器中可以訪問到將被修改的那一行數據,根據事件不同能訪問的也不同,update 可用 old 訪問舊數據,new訪問新數據,insert 可用 new 訪問新數據,delete 可用 old 訪問舊數據。

可以將 new 和 old 看做一個對象,其中封裝了修改的數據的所有字段。

使用觸發器

案例

有 cmd 表和錯誤日志表,需求:在 cmd 執行失敗時自動將信息存儲到錯誤日志表中。

# 准備數據
mysql> 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代表執行失敗
# 錯誤日志表
mysql> create table errlog(
	   id int primary key auto_increment,
	   err_cmd char(64),
	   err_time datetime);
# 創建觸發器
mysql> delimiter //
mysql> 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 if;
end //
delimiter;
# 往表 cmd 中插入記錄,觸發觸發器,根據 if 條件決定是否需要插入錯誤日志
mysql> insert into cmd(
	   user,
       priv,
       cmd,
       sub_time,
       success
) values
	   ('thales','0755','ls-l /etc',now(),'yes'),
	   ('thales','0755','cat /etc/password',now(),'no'),
	   ('thales','0755','user ass xxx',now(),'no'),
	   ('thales','0755','ps aux',now(),'yes');
# 查看錯誤日志表中的記錄是否有自動插入
mysql> select * from errlog;

delimiter用於修改默認的行結束符,由於在觸發器中有多條 sql 語句需要使用分號來結束,但是觸發器是一個整體,所以需要先更換默認的結束符(這里修改的只是客戶端的結束符,服務端還是以分號結束),在觸發器編寫完后再講結束符設置回分號

注意:外鍵不能觸發事件,主表刪除了某個主鍵,從表也會相應的刪除數據,但是並不會執行觸發器,並且觸發器中不能使用事務,相同時間點的相同事件的觸發器,不能同時存在。

刪除觸發器

語法

mysql> drop trigger trigger_name;
# 刪除上面創建的觸發器
mysql> drop trigger trigger1;

事務

什么是事務?

mysql 事務主要用於處理操作量大,復雜度高的數據。比如說,在人員管理系統中,你刪除一個人員,你即需要刪除人員的基本資料,也需要刪除和該人員相關的信息,如信箱、文章等,這樣,這些數據庫操作就構成一個事務。事務是邏輯上的一組操作,要么都成功,要么都失敗。

  • 在 mysql 中只有使用了 InnoDB 數據庫引擎的數據庫或表才支持事務;
  • 事務處理可以用來維護數據庫的完整性,保證成批的 sql 語句要么都執行,要么都不執行;
  • 事務用來管理 insert、update、delete語句

事務的四個特性

一般來說,事務必須滿足四個條件(ACID):原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability)。

  • 原子性:一個事務(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節,事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣;
  • 一致性:在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及后續數據庫可以自發性的完成預定的工作;
  • 隔離性:數據庫允許多個並發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務並發執行時由於交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(Read committed)、可重復讀(Repeatable read)和串行化(Serializable)。
  • 持久性:事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失。

在 mysql 命令行的默認設置下,事務都是自動提交的,即執行 sql 語句后就會馬上執行 commit 操作。因此要顯式的開啟一個事務必須使用命令 begin 或 start transaction,或者執行命令 set autocommit=0,用來禁止使用當前會話的自動提交。

事務控制語句

  • begin 或 start transaction:顯式的開啟一個事務;
  • commit:也可以使用 commit work,不過二者是等價的。commit 會提交事務,並使已對數據庫進行的所有修改成為永久性的;
  • rollback:也可以使用 rollback work,二者也是等價的。回滾會結束用戶的事務,並撤銷正在進行的所有未提交的修改;
  • savepoint identifier:savepoint 允許在事務中創建一個保存點,一個事務中可以有多個 savepoint;
  • release savepoint identifier:刪除一個事務的保存點,當沒有指定的保存點時,執行該語句會拋出一個異常;
  • rollback to identifier:把事務回滾到標記點;
  • set transaction:用來設置事務的隔離級別。InnoDB 存儲引擎提供事務的隔離級別有 read uncommitted、read committed、repeatable read和 serializable。

mysql 事務處理的兩種方式

  1. BEGIN,ROLLBACK,COMMIT 來實現
  • BEGIN:開始一個事務
  • ROLLBACK:事務回滾
  • COMMIT:事務確認
  1. 直接使用 set 來改變 mysql 的自動提交模式
  • SET AUTOCOMMIT=0:禁止自動提交
  • SET AUTOCOMMIT=1:開啟自動提交

事務的用戶隔離級別

數據庫使用者可以控制數據庫工作在哪個級別下,就可以防止不同的隔離性問題。

  • read uncommitted:不做任何隔離,可能臟讀、幻讀;
  • read committed:可以防止臟讀,不能防止不可重復讀和幻讀;
  • repeatable read:可以防止臟讀,不可重復讀,不能防止幻讀;
  • serializable:數據庫運行在串行化實現,所有問題都沒有,就是性能低。

修改隔離級別

查詢當前級別

mysql> select @@tx_isolation;

修改級別

mysql> set global transaction isolation level Repeatable read;

使用事務

start transaction:開啟事務,在這條語句之后的 sql 將處在同一事務,不會立即修改數據庫

commit:提交事務,讓這個事務中的 sql 立即執行數據的操作

rollback:回滾事務,取消這個事務,這個事務不會對數據庫中的數據產生任何影響。

案例:轉賬過程中發生異常

# 准備數據
mysql> create table account(
	   id int primary key auto_increment,
	   name varchar(20),
	   money double);
insert into account values(1,'趙大兒子',1000);
insert into account values(2,'劉大牛',1000);
insert into account values(3,'豬頭三',1000);
insert into account values(4,'王進',1000);
insert into account values(5,'黃卉',1000);

# 趙大兒子劉大牛佳轉賬1000塊
# 未使用事務
update account set money = money - 1000 where id = 1;
update account set moneys = money - 1000 where id = 1; # money打錯了導致執行失敗

# 在python中使用事務處理
sql = 'update account set money = money - 1000 where id = 1;'
sql2 = 'update account set moneys = money + 1000 where id = 2;' # money打錯了導致執行失敗
try:
    cursor.execute(sql)
    cursor.execute(sql2)
    conn.commit()
except:
    conn.rollback()

注意:事務的回滾的前提是能捕捉到異常,否則無法決定何時回滾,python 中很簡單就可以實現,另外 mysql 中需要使用存儲過程才可以捕獲異常。

存儲過程

什么是存儲過程?

存儲過程是一組任意的 sql 語句集合,存儲在 mysql 中,調用存儲過程時將會執行其包含的所有 sql 語句,與 python 中的函數類似。

為什么使用存儲過程?

回顧觸發器與視圖其實都是為了簡化應用程序中 sql 語句的書寫,但是還是需要編寫,而存儲過程中可以包含任何的 sql 語句,包括視圖、事務、控制流程等,這樣一來,用用程序可以從 sql 語句中完全解放出來,mysql 可以替代應用程序完成數據相關的邏輯處理。

三種開發方式對比

  1. 應用程序僅負責業務邏輯編寫,所有與數據相關的邏輯都交給 mysql 來完成,通過存儲過程(推薦使用)

    優點:應用程序與數據處理完全解耦合,一對復雜的 sql 被封裝成了一個簡單的存儲過程,考慮到網絡環境因素,效率高,應用程序開發者不需要編寫 sql 語句,開發效率高。

    缺點:python 語法與 mysql 語法區別巨大,學習成本高,並且各種數據庫的語法大不相同,所以移植性非常差,應用程序開發者與 DBA 的跨部門溝通成本高,造成整體效率低。

  2. 應用程序不僅編寫業務邏輯,還需要編寫所有的 sql 語句

    優點:擴展性高,對於應用程序開發者而言,擴展性和維護性相較於第一種都有所提高。

    缺點:執行效率低,由於需要將對象的操作轉化為 sql 語句,且需要通過網絡發送大量的 sql 語句。

創建存儲過程

語法

mysql> create procedure pro_name(p_type p_name data_type)
begin
sql 語句......流程控制
end

p_type:參數類型

in:表示輸入參數

out:表示輸出參數

inout:表示既能輸入又能輸出

p_name:參數名稱

data_type:參數類型 mysql 支持的所有數據類型

案例:使用存儲過程完成對 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 chineseXXX > m and chinese < n; 修改錯誤的列名以測試執行失敗
    set res = 100;
end//
delimiter ;
set @res = 0;
#調用存儲過程
call p1(70,80,@res);
#查看執行結果
select @res;

注意:存儲過程的 out 類參數必須是一個變量,用來裝輸出數據的,不可是一個值

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 會自動將參數都設置一個變量所以可以直接傳入一個值,當然值如果作為輸出參數的話,傳入什么都可以。

刪除存儲過程

drop procedure 過程名;

修改存儲過程的意義不大,不如刪除重寫。

查看存儲過程

# 當前庫所有存儲過程名稱
mysql> select 'name' from mysql.proc where db = 'db02' and 'type' = 	 'procedure';
# 查看創建語句
mysql> show create procedure p1;

存儲過程中的事務應用

存儲過程中支持任何的 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);
select @res;

總結:拋開溝通成本、學習成本,存儲過程無疑是效率最高的處理方式。

函數

內置函數

在SQL 語句中,表達式可用於一些諸如SELECT語句的ORDER BY 或 DELETE或 UPDATE語句的 WHERE 子句或 SET語句之類的地方。使用文本值、column值、NULL值、函數、 操作符來書 寫 表達式。 本章敘述了可用於書寫MySQL表達式的函數和操作符。

這些內置函數大大提高了我們的開發效率

字符相關函數

image-20181126193527077

數學相關函數

image-20181126193551302

日期相關函數

其他函數

自定義函數

語法

mysql> create function f_name(paramters)
return dataType
return value;

說明:paramters 只能是 in 輸入參數、參數名、類型必須有返回值,不能加 begin 和 end,returns 后面是返回值的類型,這里不加分號,return 后面是要返回的值。

案例:將兩數相加

mysql> create function addfuntion(a int,b int)
returns int return a + b;
# 執行函數
mysql> select addfuntion(1,1);

注意:函數只能返回一個值,函數一般不涉及數據的增刪查改,就是一個通用的功能,調用自定義的函數與調用系統的一直,不需要 call 使用 select 可獲得返回值,函數中不能使用 sql 語句,就像在 java 中不能識別 sql 語句一樣(沒學過java。。。)

數據備份

使用 mysqldump 程序進行備份

mysqldump -u -p db_name [table_name,,,] > fileName.sql

注意:這是命令行命令

可以選則要備份那些表,如果不指定代表全部備份

# 示例
# 單庫備份
mysqldump -uroot -p123 db1 > db1.sql
mysqldump -uroot -p123 db1 table table2 > db1-table1-table2.sql

# 多庫備份
mysqldump -uroot -p123 --databases db1 db2 mysql db3 > db1_db2_mysql_db3.sql

# 備份所有
mysqldump -uroot -p123 --all-databases > all.sql

使用 mysql 進行恢復

  1. 退出數據庫后
mysql -u -p < filenam.sql
  1. 不用退出數據庫
    1. 創建空數據庫
    2. 選擇數據庫
    3. 然后使用 source filename 來進行還原
mysql> use db1;
mysql> source /root/db1.sql

數據庫遷移

# 務必保證在相同版本之間遷移
mysqldump -h 源ip -uroot -p123 --databases db1 | mysql -h 目標ip -uroot -p456

流程控制

if 語句

if 條件 then 語句;end if;第二種 if else if 條件 then 語句1;else if 條件

then 語句2;else 語句3;end if;

案例:編寫過程實現 輸入一個整數 type 范圍1-2 輸出 type=1 or type=other;

mysql> create procedure showType(in type int,out result char(20))
begin
if type = 1 then 
set result = "type = 1";
elseif type = 2 then 
set result = "type = 2";
else 
set result = "type = other";
end if;
end

case 語句

與 switch 一樣,進行選擇執行

mysql> 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

定義變量

mysql> declare 變量名 類型 default 值;
mysql> declare i int default 0;

while 循環

# 循環輸出10次 hello mysql
mysql> create procedure showHello()
begin
declare i int default 0;
while i < 10 do
select 'hello mysql';
end while;
end

loop 循環

沒有條件,需要自己定義結束語句

# 輸出10次 hello mysql
mysql> create procedure showLoop()
begin
declare i int default 0;
aloop:loop
select 'hello loop';
set i > 9 then leave aloop;
end if;
end loop aloop;
end

repeat 循環

# 類似do while
# 輸出10次hello repeat
mysql> 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之間的奇數
mysql> 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM