1. 程式人生 > 其它 >MySQL知識補充(表字段操作、檢視、觸發器、事物、儲存過程、內建函式、流程控制、索引、慢查詢)

MySQL知識補充(表字段操作、檢視、觸發器、事物、儲存過程、內建函式、流程控制、索引、慢查詢)

今日內容概要

  • 表字段操作補充(掌握)
  • 檢視(瞭解)
  • 觸發器(瞭解)
  • 事務(掌握)
  • 儲存過程(瞭解)
  • 內建函式(瞭解)
  • 流程控制(瞭解)
  • 索引(熟悉)

內容詳細

1、表字段操作補充

# 1.新增表字段
	alter table 表名 add 欄位名 欄位型別 約束條件;  # 預設尾部追加

	alter table 表名 add 欄位名 欄位型別 約束條件 after 已經存在的欄位名;  # 新增欄位到指定的欄位之後

	alter table 表名 add 欄位名 欄位型別 約束條件 first;  # 新增欄位到首行 

# 2.修改欄位
"""
modify只能改欄位資料型別完整約束,不能改欄位名,但是change可以!
"""
	alter table 原表名 rename 新表名;  # 修改表名
	alter table 表名 change 原欄位名 新欄位名 新型別;  # 同時修改欄位名和型別
	alter table 表名 modify 欄位名 新欄位型別;  # 修改欄位型別

# 3.刪除欄位
	alter table 表名 drop 欄位名;

2、檢視

# 1、什麼是檢視
	檢視就是通過查詢得到一張虛擬表,然後儲存下來,下次直接使用即可

# 2、為什麼要用檢視
	如果要頻繁使用一張虛擬表,可以不用重複查詢

# 3、如何用檢視
"""
以課程表 老師表為例
"""
create view teacher2course as select * from teacher inner join course on teacher.tid = course.teacher_id;
    
"""
建立好了之後 驗證它的存在:
	navicat驗證 cmd終端驗證
	
最後檔案驗證 得出下面的結論:
	檢視只有表結構資料還是來源於之前的表
"""  

# 4、強調
	1、在硬碟中,檢視只有表結構檔案,沒有表資料檔案
	2、檢視通常是用於查詢,儘量不要修改檢視中的資料
    
"""
檢視如果不用了 最好刪除:
	drop view teacher2course;
"""

# 5、開發過程中會不會去使用檢視?
	一般不會!
	檢視是mysql的功能,如果你的專案裡面大量的使用到了檢視,那意味著你後期想要擴張某個功能的時候這個功能恰巧又需要對檢視進行修改,意味著你需要先在mysql這邊將檢視先修改一下,然後再去應用程式中修改對應的sql語句,這就涉及到跨部門溝通的問題,所以通常不會使用檢視,而是通過重新修改sql語句來擴充套件功能

3、觸發器(瞭解)

# 在滿足對某張表資料的增、刪、改的情況下,自動觸發的功能稱之為觸發器

# 為何要用觸發器
	觸發器專門針對我們對某一張表資料增insert、刪delete、改update的行為
	這類行為一旦執行就會觸發觸發器的執行,即自動執行另外一段sql程式碼

# 建立觸發器語法
"""
語法結構
	create trigger 觸發器的名字 before/after insert/update/delete on 表名 for each row
begin
	sql語句
end
"""

# 針對插入
create trigger tri_after_insert_t1 after insert on 表名 for each row
begin
	sql程式碼。。。
end 

create trigger tri_after_insert_t2 before insert on 表名 for each row
begin
	sql程式碼。。。
end

# 針對刪除
create trigger tri_after_delete_t1 after delete on 表名 for each row
begin
	sql程式碼。。。
end

create trigger tri_after_delete_t2 before delete on 表名 for each row
begin
	sql程式碼。。。
end

# 針對修改
create trigger tri_after_update_t1 after update on 表名 for each row
begin
	sql程式碼。。。
end

create trigger tri_after_update_t2 before update on 表名 for each row
begin
	sql程式碼。。。
end

"""
需要注意 在書寫sql程式碼的時候結束符是; 而整個觸發器的結束也需要分號;
這就會出現語法衝突 需要我們臨時修改結束符號:
	delimiter $$  # 將sql語句結束符號改為 $$
	delimiter ; 
該語法只在當前視窗有效  
"""


# 案例
"""
cmd 表 errlog 表
往cmd表中存入資料為 no時 自動觸發觸發器插入錯誤日誌到errlog 表
"""
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 $$  # 將mysql預設的結束符由;換成$$
create trigger tri_after_insert_cmd after insert on cmd for each row
begin
    if NEW.success = 'no' then  # 新記錄都會被MySQL封裝成NEW物件
        insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time);
    end if;
end $$
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');
    
# 查詢errlog表記錄驗證
select * from errlog;

# 刪除觸發器
drop trigger tri_after_insert_cmd;

4、事務(掌握)

# 1、什麼是事務
	開啟一個事務可以包含一些sql語句,這些sql語句要麼同時成功,要麼一個都別想成功,稱之為事務的原子性

# 2、事務的作用
	保證了對資料操作的資料安全性
	案例:用交行的卡操作建行ATM機給工商的賬戶轉錢

# 3、事務應該具有4個屬性:
	原子性、一致性、隔離性、永續性
	這四個屬性通常稱為 ACID特性

# 原子性(atomicity)
	一個事務是一個不可分割的工作單位,事務中包括的諸操作要麼都做,要麼都不做

# 一致性(consistency)
	事務必須是使資料庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的

# 隔離性(isolation)
	一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的資料對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾

# 永續性(durability)
	永續性也稱永久性(permanence),指一個事務一旦提交,它對資料庫中資料的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響

    
# 4、如何用
create table user(
id int primary key auto_increment,
name char(32),
balance int
);

insert into user(name,balance)
values
('jason',1000),
('egon',1000),
('tank',1000);

# 修改資料之前先開啟事務操作
start transaction;

# 修改操作
update user set balance=900 where name='jason';  # 買支付100元
update user set balance=1010 where name='egon';  # 中介拿走10元
update user set balance=1090 where name='tank';  # 賣家拿到90元

# 回滾到上一個狀態
rollback;

# 開啟事務之後,只要沒有執行commit操作,資料其實都沒有真正重新整理到硬碟
commit;
"""開啟事務檢測操作是否完整,不完整主動回滾到上一個狀態,如果完整就應該執行commit操作"""

# 站在python程式碼的角度,應該實現的虛擬碼邏輯,
try:
    update user set balance=900 where name='jason';  # 買支付100元
    update user set balance=1010 where name='egon';  # 中介拿走10元
    update user set balance=1090 where name='tank';  # 賣家拿到90元
except 異常:
    rollback;
else:
    commit;

5、儲存過程(瞭解)

# 儲存過程包含了一系列可執行的sql語句,儲存過程存放於MySQL中,通過呼叫它的名字可以執行其內部的一堆sql,類似於python中的自定義函式

# 1、基本使用
delimiter $$
create procedure p1()
begin
	select * from user;
end $$
delimiter ;

# 呼叫
call p1();


# 2、建立儲存過程
delimiter $$
create procedure p2(
    in m int,  # in 表示這個引數必須只能是傳入不能被返回出去
    in n int,  
    out res int  # out 表示這個引數可以被返回出去
)
begin
    select tname from teacher where tid > m and tid < n;
    set res=0;  # 用來標誌儲存過程是否執行
end $$
delimiter ;

# 針對res需要先提前定義 MySQL也是可以定義變數的
set @res=10;  # 定義
select @res;  # 檢視
call p2(1,5,@res);  # 呼叫


# 3、如何用儲存過程
"""大前提:儲存過程在哪個庫下面建立的只能在對應的庫下面才能使用!!!"""

# 3.1 直接在mysql中呼叫
set @res=10  # res的值是用來判斷儲存過程是否被執行成功的依據,所以需要先定義一個變數@res儲存10
call p2(2,4,10);  # 報錯
call p2(2,4,@res);  

# 檢視結果
select @res;  # 執行成功,@res變數值發生了變化

# 3.2 在python程式中呼叫
pymysql連結mysql
產生的遊表cursor.callproc('p2',(2,4,10))  # 內部原理:@_p2_0=2,@_p2_1=4,@_p2_2=10;
cursor.excute('select @_p2_2;')


# 3.3 儲存過程與事務使用舉例(瞭解)
delimiter //
create PROCEDURE p5(
    OUT p_return_code tinyint
)
BEGIN
    DECLARE exit handler for sqlexception
    BEGIN
        -- ERROR
        set p_return_code = 1;
        rollback;
    END;

  DECLARE exit handler for sqlwarning
  BEGIN
      -- WARNING
      set p_return_code = 2;
      rollback;
  END;

  START TRANSACTION;
      update user set balance=900 where id =1;
      update user123 set balance=1010 where id = 2;
      update user set balance=1090 where id =3;
  COMMIT;

  -- SUCCESS
  set p_return_code = 0;  # 0代表執行成功

END //
delimiter ;

6、內建函式(瞭解)

# 注意與儲存過程的區別,mysql內建的函式只能在sql語句中使用
CREATE TABLE blog (
    id INT PRIMARY KEY auto_increment,
    NAME CHAR (32),
    sub_time datetime
);

INSERT INTO blog (NAME, sub_time)
VALUES
    ('第1篇','2015-03-01 11:31:21'),
    ('第2篇','2015-03-11 16:31:21'),
    ('第3篇','2016-07-01 10:21:31'),
    ('第4篇','2016-07-22 09:23:21'),
    ('第5篇','2016-07-23 10:11:11'),
    ('第6篇','2016-07-25 11:21:31'),
    ('第7篇','2017-03-01 15:33:21'),
    ('第8篇','2017-03-01 17:32:21'),
    ('第9篇','2017-03-01 18:31:21');

# 統計每個月份文章的篇數
select date_format(sub_time,'%Y-%m'),count(id) from blog group by date_format(sub_time,'%Y-%m');

參考部落格:http://www.cnblogs.com/linhaifeng/articles/7495918.html

7、流程控制(瞭解)

# if條件語句
delimiter //
CREATE PROCEDURE proc_if ()
BEGIN
    declare i int default 0;
    if i = 1 THEN
        SELECT 1;
    ELSEIF i = 2 THEN
        SELECT 2;
    ELSE
        SELECT 7;
    END IF;

END //
delimiter ;


# while迴圈
delimiter //
CREATE PROCEDURE proc_while ()
BEGIN
    DECLARE num INT ;
    SET num = 0 ;
    WHILE num < 10 DO
        SELECT num ;
        SET num = num + 1 ;
    END WHILE ;

END //
delimiter ;

8、索引(熟悉)

# 資料都是存在硬碟上的,那查詢資料不可避免的需要進行IO操作
	索引就是一種資料結構,類似於書的目錄。意味著以後再查資料應該先找目錄再找資料,而不是用翻頁的方式查詢資料

# 索引在MySQL中也叫做 “鍵”,是儲存引擎用於快速找到記錄的一種資料結構
	primary key  
	unique key
	index key
"""
注意foreign key不是用來加速查詢用的,不在我們研究範圍之內

上面三種key前兩種除了有加速查詢的效果之外還有額外的約束條件:
	primary key:非空且唯一,unique key:唯一
	而 index key沒有任何約束功能 只會幫你加速查詢

本質都是:
	通過不斷地縮小想要獲取資料的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查詢方式來鎖定資料
"""

# 索引的影響:
	在表中有大量資料的前提下,建立索引速度會很慢
	在索引建立完畢後,對錶的查詢效能會大幅度提升,但是寫的效能會降低

# b+樹
	只有葉子結點存放真實資料,根和樹枝節點存的僅僅是虛擬資料

	查詢次數由樹的層級決定,層級越低次數越少

	一個磁碟塊兒的大小是一定的,那也就意味著能存的資料量是一定的
    
# 如何保證樹的層級最低呢?
	一個磁碟塊兒存放佔用空間比較小的資料項時

# 思考:我們應該給我們一張表裡面的什麼欄位欄位建立索引能夠降低樹的層級高度?
	>>>主鍵id欄位

8.1、索引(二)

# 1、聚集索引(primary key)
	聚集索引其實指的就是表的主鍵,innodb引擎規定一張表中必須要有主鍵

	innodb在建表的時候對應到硬碟有幾個檔案(兩個)?
		frm檔案只存放表結構,不可能放索引,也就意味著innodb的索引跟資料都放在idb表資料檔案中。

# 特點:
    葉子結點放的一條條完整的記錄


# 2、輔助索引(unique,index)
	輔助索引:查詢資料的時候不可能都是用id作為篩選條件,也可能會用name,password等欄位資訊,那麼這個時候就無法利用到聚集索引的加速查詢效果。就需要給其他欄位建立索引,這些索引就叫輔助索引

# 特點:
	葉子結點存放的是輔助索引欄位對應的那條記錄的主鍵的值(比如:按照name欄位建立索引,那麼葉子節點存放的是:{name對應的值:name所在的那條記錄的主鍵值})

	select name from user where name='jason';
	上述語句叫覆蓋索引:只在輔助索引的葉子節點中就已經找到了所有我們想要的資料

	select age from user where name='jason';
	上述語句叫非覆蓋索引,雖然查詢的時候命中了索引欄位name,但是要查的是age欄位,所以還需要利用主鍵才去查詢

    
    
# 3、測試索引
# 3.1. 準備表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);

# 3.2. 建立儲存過程,實現批量插入記錄
delimiter $$  # 宣告儲存過程的結束符號為$$
create procedure auto_insert1()
BEGIN
    declare i int default 1;
    while(i<1000000)do
        insert into s1 values(i,'jason','male',concat('jason',i,'@oldboy'));
        set i=i+1;
    end while;
END$$  # $$結束
delimiter ;  # 重新宣告分號為結束符號

# 3.3. 檢視儲存過程
show create procedure auto_insert1\G 

# 3.4. 呼叫儲存過程
call auto_insert1();


# 表沒有任何索引的情況下
select * from s1 where id=30000;
# 避免列印帶來的時間損耗
select count(id) from s1 where id = 30000;
select count(id) from s1 where id = 1;

# 給id做一個主鍵
alter table s1 add primary key(id);  # 速度很慢

select count(id) from s1 where id = 1;  # 速度相較於未建索引之前兩者差著數量級
select count(id) from s1 where name = 'jason';  # 速度仍然很慢


"""
範圍問題
"""
# 並不是加了索引,以後查詢的時候按照這個欄位速度就一定快   
select count(id) from s1 where id > 1;  # 速度相較於id = 1慢了很多
select count(id) from s1 where id >1 and id < 3;
select count(id) from s1 where id > 1 and id < 10000;
select count(id) from s1 where id != 3;

alter table s1 drop primary key;  # 刪除主鍵 單獨再來研究name欄位
select count(id) from s1 where name = 'jason';  # 又慢了

create index idx_name on s1(name);  # 給s1表的name欄位建立索引
select count(id) from s1 where name = 'jason';  # 仍然很慢!!!

"""
再來看b+樹的原理,資料需要區分度比較高,而我們這張表全是jason,根本無法區分
那這個樹其實就建成了“一根棍子”
"""
select count(id) from s1 where name = 'xxx';  
# 這個會很快,我就是一根棍,第一個不匹配直接不需要再往下走了
select count(id) from s1 where name like 'xxx';
select count(id) from s1 where name like 'xxx%';
select count(id) from s1 where name like '%xxx';  # 慢 最左匹配特性

# 區分度低的欄位不能建索引
drop index idx_name on s1;

# 給id欄位建普通的索引
create index idx_id on s1(id);
select count(id) from s1 where id = 3;  # 快了
select count(id) from s1 where id*12 = 3;  # 慢了  索引的欄位一定不要參與計算

drop index idx_id on s1;
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';
# 針對上面這種連續多個and的操作,mysql會從左到右先找區分度比較高的索引欄位,先將整體範圍降下來再去比較其他條件
create index idx_name on s1(name);
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';  # 並沒有加速

drop index idx_name on s1;
# 給name,gender這種區分度不高的欄位加上索引並不難加快查詢速度

create index idx_id on s1(id);
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';  # 快了  先通過id已經講資料快速鎖定成了一條了
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 慢了  基於id查出來的資料仍然很多,然後還要去比較其他欄位

drop index idx_id on s1;

create index idx_email on s1(email);
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 快 通過email欄位一劍封喉 


# 聯合索引

select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  
# 如果上述四個欄位區分度都很高,那給誰建都能加速查詢
# 給email加然而不用email欄位
select count(id) from s1 where name='jason' and gender = 'male' and id > 3; 
# 給name加然而不用name欄位
select count(id) from s1 where gender = 'male' and id > 3; 
# 給gender加然而不用gender欄位
select count(id) from s1 where id > 3; 

# 帶來的問題是所有的欄位都建了索引然而都沒有用到,還需要花費四次建立的時間
create index idx_all on s1(email,name,gender,id);  # 最左匹配原則,區分度高的往左放
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 速度變快

9、慢查詢日誌

設定一個時間檢測所有超出該時間的sql語句,然後針對性的進行優化!