資料庫(七)
前言
本篇部落格學習內容為檢視、觸發器、事務、儲存過程、函式、資料備份及流程控制。
檢視
什麼是檢視?
檢視是由一張表或多張表的查詢結果構成的一張虛擬表,建立一張檢視後會在資料庫中保留一個以 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 指定哪些欄位要出現在檢視中。 注意:由於是一張虛擬表,檢視中的資料實際來源於其他表,所以在檢視中的資料不會出現在硬碟上,也就是隻會儲存一份資料結構。
使用檢視
檢視是一張虛擬表,所以使用方式與普通表沒有區別。
檢視檢視
- 檢視資料結構
mysql> desc view_name;
- 檢視建立語句
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 事務處理的兩種方式
- BEGIN,ROLLBACK,COMMIT 來實現
- BEGIN: 開始一個事務
- ROLLBACK: 事務回滾
- COMMIT: 事務確認
- 直接使用 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 可以替代應用程式完成資料相關的邏輯處理。
三種開發方式對比
-
應用程式僅負責業務邏輯編寫,所有與資料相關的邏輯都交給 mysql 來完成,通過儲存過程(推薦使用)
優點:應用程式與資料處理完全解耦合,一對複雜的 sql 被封裝成了一個簡單的儲存過程,考慮到網路環境因素,效率高,應用程式開發者不需要編寫 sql 語句,開發效率高。
缺點:python 語法與 mysql 語法區別巨大,學習成本高,並且各種資料庫的語法大不相同,所以移植性非常差,應用程式開發者與 DBA 的跨部門溝通成本高,造成整體效率低。
-
應用程式不僅編寫業務邏輯,還需要編寫所有的 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 中呼叫儲存過程
importpymysql #建立連線 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表示式的函式和操作符。
這些內建函式大大提高了我們的開發效率
字元相關函式
數學相關函式
日期相關函式
其他函式
自定義函式
語法
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 進行恢復
- 退出資料庫後
mysql -u -p < filenam.sql
- 不用退出資料庫
- 建立空資料庫
- 選擇資料庫
- 然後使用 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 1then select "type = 1"; when 2then 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