視圖 create view ... as ps:SQL文件在上一篇博客末尾 視圖就是通過查詢得到一張虛擬表,然後保存下來,下次直接使用 create view teacher_course as select * from teacher inner join course on teacher. ...
目錄
視圖 create view ... as
ps:SQL文件在上一篇博客末尾
視圖就是通過查詢得到一張虛擬表,然後保存下來,下次直接使用
create view teacher_course as
select * from teacher inner join course on teacher.tid = course.teacher_id;
1.視圖的表只能用來查詢不能做其他增刪改操作 視圖的數據來源於原表
2.視圖儘量少用 會跟真正的表產生混淆 從而干擾操作者 終端里視圖和原表放在一起,Navicat會把視圖單獨存放在views。
將課程表與教師表拼接產生視圖:
視圖表無法刪除:
觸發器
簡介
達到某個條件之後自動觸發執行
在MySQL中更加詳細的說明是觸發器:針對錶繼續增、刪、改操作能夠自動觸發
主要有六種情況:增前、增後、刪前、刪後、改前、改後
創建觸發器的語法 create trigger
create trigger 觸發器的名字 before/after insert/update/delete on 表名 for each row
begin
sql語句
end
觸發器命名有一定的規律
觸發器的命名不是硬性規範,想自定義名字也可以。
tri_before_insert_t1
tri_after_delete_t2
tri_after_update_t2
臨時修改SQL語句的結束符 delimiter
因為有些操作中需要使用分號 使用關鍵字更換SQL語句的結束符
delimiter $$ /* 臨時將結束符更改為$$ */
delimiter ; /* 用完記得改回來 */
觸發器的實際運用
/* 創建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代表執行失敗
);
/* 創建錯誤日誌表 */
CREATE TABLE errlog (
id INT PRIMARY KEY auto_increment,
err_cmd CHAR (64),
err_time datetime
);
/* 創建觸發器
1.tri_after_insert_cmd 表示cmd表增加數據後觸發 名字可以修改 這樣寫比較規範
2.新增的數據的success欄位如果為no,則會向errlog表插入一條數據,這條數據包含cmd表新增數據的信息。
3.NEW指代的當前cmd表的每一條數據對象,相當於每次往cmd表插入的數據。
*/
delimiter $$ # 將mysql預設的結束符由;換成$$
create trigger tri_after_insert_cmd after insert on cmd for each row
begin # 表示SQL語句開始
if NEW.success = 'no' then # 新記錄都會被MySQL封裝成NEW對象
insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time);
end if; # 表示if語句結束,加上就行了。
end $$ # SQL語句結束 再加上結束符$$
delimiter ; # 結束之後記得再改回來,不然後面結束符就都是$$了
無註釋版:
delimiter $$
create trigger helloworld 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的條件決定是否插入錯誤日誌
#NOW()函數可以獲取當前的時間
INSERT INTO cmd (
USER,
priv,
cmd,
sub_time,
success
)
VALUES
('kevin','0755','ls -l /etc',NOW(),'yes'),
('kevin','0755','cat /etc/passwd',NOW(),'no'),
('kevin','0755','useradd xxx',NOW(),'no'),
('kevin','0755','ps aux',NOW(),'yes');
# 查詢errlog表記錄
select * from errlog;
給cmd表插入數據之後,查看errlog表:
觸發器補充方法 show triggers\drop trigger
- 查看所有的觸發器:
show triggers;
trigger --> 觸發器的名字
event:INSERT --> 在發生插入的時候觸發
table --> 發動觸發器的表
statement --> sql語句
timming --> 觸發的時機(這裡是在插入之後) - 刪除觸發器:
drop trigger tri_after_insert_cmd;
- 新增多個觸發器時報錯:
這個版本的MySQL還不支持“同一個表中具有相同操作時間和事件的多個觸發器”。
事務
事務的四大特性 ACID
事務的四大特性(ACID)
A:原子性
事務中的各項操作是不可分割的整體 要麼同時成功要麼同時失敗
比如銀行轉賬 你的賬戶增加錢 別人賬戶扣錢 要麼同時成功要麼同時失敗
C:一致性
使資料庫從一個一致性狀態變到另一個一致性狀態
I:隔離性
多個事務之間彼此不幹擾
他給你轉錢,你給他轉錢,互不幹擾(操作數據時互不幹擾)
D:持久性
也稱永久性,指一個事務一旦提交,它對資料庫中數據的改變就應該是永久性的
轉賬成功之後,賬戶的錢就永久性的減少了,不可能回退到轉賬之前的狀態。
事務實際運用 start transaction
/* 創建用戶表 */
create table user(
id int primary key auto_increment,
name char(32),
balance int
);
/* 添加基礎數據 */
insert into user(name,balance)
values
('jason',1000),
('kevin',1000),
('tank',1000);
/* 修改數據之前先開啟事務操作 */
start transaction;
/* 修改操作 */
update user set balance=900 where name='jason'; #買支付100元
update user set balance=1010 where name='kevin'; #中介拿走10元
update user set balance=1090 where name='tank'; #賣家拿到90元
/* 回滾到上一個狀態 */
rollback;
/* 開啟事務之後,只要沒有執行commit操作,數據其實都沒有真正刷新到硬碟*/
commit;
/*
回滾怎麼實現?
沒確認時,改的是記憶體中的數據,不影響硬碟中的真實數據。
當進行確認,才會將記憶體的數據 保存到硬碟 永久修改
執行rollback commit意味著事務結束。
*/
rollback:
commit確認之後無法rollback:
事務相關關鍵字 savepoint
"""
事務相關關鍵字
start transaction;
rollback
commit
savepoint 保留點
部分事務 和 完整事務
"""
為了支持回退部分事務處理,必須能在事務處理塊中合適的位置放置占位符,
這樣如果需要回退可以回退到某個占位符(保留點)
# ps:但這也打破了事務的原子性,所以使用的少
創建占位符:savepoint sp01;
回退到占位符地址: rollback to sp01;
事務的隔離級別(重要)
事務需要對數據進行操作,所以事務與事務之間需要隔離操作,防止產生錯誤。
在SQL標準中定義了四種隔離級別,每一種級別都規定了一個事務中所做的修改
InnoDB支持所有隔離級別
set transaction isolation level 級別
1.read uncommitted(未提交讀)
事務中的修改即使沒有提交,對其他事務也都是可見的,事務可以讀取未提交的數據,這一現象也稱之為"臟讀"
2.read committed(提交讀)
大多數資料庫系統預設的隔離級別
一個事務從開始直到提交之前所作的任何修改對其他事務都是不可見的,這種級別也叫做"不可重覆讀"
3.repeatable read(可重覆讀)
- 可重覆讀是MySQL預設隔離級別。
- 能夠解決"臟讀"問題,但是無法解決"幻讀"
所謂幻讀指的是當某個事務在讀取某個範圍內的記錄時另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄會產生幻行,InnoDB和XtraDB通過多版本併發控制(MVCC)及間隙鎖策略解決該問題 - 幻讀的例子
某事務讀取資料庫發現有3條數據 結果另一個事物刪了一條數據 此時先前的事務讀取的信息就錯了,故成為幻讀。 - 如何解決幻讀?
在數據上做特殊標記,數據被修改,這個唯一標識會變。事務修改時檢查標識,一旦發現標識和之前讀取的時候不同,就重新讀資料庫。
4.serializable(可串列讀)
強制事務串列執行,很少使用該級別
一次性只有一個事務操作數據,慢的離譜。
多版本併發控制 MVCC
MVCC只能在read committed(提交讀)、repeatable read(可重覆讀)兩種隔離級別下工作,其他兩個不相容(read uncommitted:總是讀取最新 serializable:所有的行都加鎖)
InnoDB的MVCC通過在每行記錄後面保存兩個隱藏的列來實現MVCC
一個列保存了行的創建時間
一個列保存了行的過期時間(或刪除時間) # 本質是系統版本號
每開始一個新的事務版本號都會自動遞增,事務開始時刻的系統版本號會作為事務的版本號用來和查詢到的每行記錄版本號進行比較
例如
剛插入第一條數據的時候,我們預設事務id為1,實際是這樣存儲的
username create_version delete_version
jason 1
可以看到,我們在content列插入了kobe這條數據,在create_version這列存儲了1,1是這次插入操作的事務id。
然後我們將jason修改為jason01,實際存儲是這樣的
username create_version delete_version
jason 1 2
jason01 2
可以看到,update的時候,會先將之前的數據delete_version標記為當前新的事務id,也就是2,然後將新數據寫入,將新數據的create_version標記為新的事務id
當我們刪除數據的時候,實際存儲是這樣的
username create_version delete_version
jason01 2 3
"""
由此當我們查詢一條記錄的時候,只有滿足以下兩個條件的記錄才會被顯示出來:
1.當前事務id要大於或者等於當前行的create_version值,這表示在事務開始前這行數據已經存在了。
2.當前事務id要小於delete_version值,這表示在事務開始之後這行記錄才被刪除。
"""
存儲過程 procedure
有參函數和無參函數
/* 可以看成是python中的自定義函數 */
# 無參函數
delimiter $$
create procedure p1()
begin
select * from cmd;
end $$
delimiter ;
# 調用
call p1()
# 有參函數
delimiter $$
create procedure p2(
in m int, # in表示這個參數必須只能是傳入不能被返回出去
in n int,
out res int # out表示這個參數可以被返回出去,還有一個inout表示即可以傳入也可以被返回出去
)
begin
select * from cmd where id > m and id < n;
set res=0; # 用來標誌存儲過程是否執行
end $$
delimiter ;
# 針對res需要先提前定義
set @res=10; 定義
select @res; 查看
call p1(1,5,@res) 調用
select @res 查看
/*
1. 註意 in out inout
2. 為什麼要使用set定義? 需要一個變數接受返回值
3. 為什麼要@? @指向函數里的返回值res
*/
存儲過程相關關鍵字 show procedure status
查看存儲過程具體信息
show create procedure pro1;
查看所有存儲過程
show procedure status;
刪除存儲過程
drop procedure pro1;
在mysql和python中使用
# 大前提:存儲過程在哪個庫下麵創建的只能在對應的庫下麵才能使用!!!
# 1、直接在mysql中調用
set @res=10 # res的值是用來判斷存儲過程是否被執行成功的依據,所以需要先定義一個變數@res存儲10
call p1(2,4,10); # 報錯
call p1(2,4,@res);
# 查看結果
select @res; # 執行成功,@res變數值發生了變化
# 2、在python程式中調用
pymysql鏈接mysql
產生的游表cursor.callproc('p1',(2,4,10)) # 內部原理:@_p1_0=2,@_p1_1=4,@_p1_2=10;
cursor.excute('select @_p1_2;')
函數
可以看成是python中的內置函數
mysql的內置函數只能在SQL語句中使用
常用函數 soundex
/* 可以通過help 函數名 查看幫助信息!" */
# 1.移除指定字元
Trim、LTrim、RTrim
# 2.大小寫轉換
Lower、Upper
# 3.獲取左右起始指定個數字元
Left、Right
# 4.返回讀音相似值(對英文效果)
Soundex
/*
eg:客戶表中有一個顧客登記的用戶名為J.Lee
但如果這是輸入錯誤真名其實叫J.Lie,可以使用soundex匹配發音類似的
where Soundex(name)=Soundex('J.Lie')
*/
# 5.日期格式:date_format
'''在MySQL中表示時間格式儘量採用2022-11-11形式'''
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');
1.where Date(sub_time) = '2015-03-01'
2.where Year(sub_time)=2016 AND Month(sub_time)=07;
# 更多日期處理相關函數
adddate 增加一個日期
addtime 增加一個時間
datediff計算兩個日期差值
Left函數只要數據的指定個數的字元:
返回讀音相似的值soundex(只對英文有效):
date_format實現真實業務邏輯(將博客按照年月分組):
- blog表展示
- 按照年月分組
流程式控制制
分支結構 IF THEN
declare i int default 0;
IF i = 1 THEN
SELECT 1;
ELSEIF i = 2 THEN
SELECT 2;
ELSE
SELECT 7;
END IF;
迴圈結構 WHILE DO
DECLARE num INT ;
SET num = 0 ;
WHILE num < 10 DO
SELECT num ;
SET num = num + 1 ;
END WHILE ;
索引
索引的概念
1)索引就好比一本書的目錄,它能讓你更快的找到自己想要的內容
2)讓獲取的數據更有目的性,從而提高資料庫檢索數據的性能
MySQL中的索引 index key
MySQL索引主要有兩種結構:B+Tree索引和Hash索引。
索引在MySQL中也叫做“鍵”,是存儲引擎用於快速找到記錄的一種數據結構,mysql有以下幾種鍵:
* primary key
* unique key
* index key
'''
1.上述的三個key都可以加快數據查詢
2.primary key和unique key除了可以加快查詢本身還自帶限制條件而index key很單一就是用來加快數據查詢
3.外鍵不屬於索引鍵的範圍 是用來建立關係的 與加快查詢無關
'''
索引加快查詢的本質
id int primary key auto_increment,
name varchar(32) unique,
province varchar(32)
age int
phone bigint
select name from userinfo where phone=18818888888; # 一頁頁的翻
select name from userinfo where id=99999; # 按照目錄確定頁數找
/*
1. 索引可以加快數據查詢 但是會降低增刪的速度 (發生增刪改操作時,會重新建立索引)
2. 通常情況下我們頻繁使用某些欄位查詢數據,為了提升查詢的速度可以將該欄位建立索引
*/
聚焦索引 輔助索引 覆蓋索引 非覆蓋索引
聚集索引(primary key)
主鍵、主鍵索引
輔助索引(unique,index)
除主鍵意外的都是輔助索引
輔助索引在查詢數據的時候還是要藉助於聚集索引
/*查詢順序: 輔助索引樹 --> 主鍵索引 --> 主鍵索引樹 -->真實數據 */
覆蓋索引
select name from user where name='jason';
非覆蓋索引
select age from user where name='jason';
索引的數據結構
索引底層其實是樹結構>>>:樹是電腦底層的數據結構
'''註意:樹結構每一個節點存放的數據大小是固定的'''
樹有很多中類型
二叉樹、b樹、b+樹、B*樹......
二叉樹
# 二叉樹
二叉樹裡面還可以細分成很多領域 我們簡單的瞭解即可
二叉意味著每個節點最大隻能分兩個子節點
二叉樹的特點:每個節點最多有2個分叉,左子樹和右子樹數據順序左小右大。
根節點:樹的最頂端的節點。(根節點只有一個)
枝節點:除根節點之外,並且本身下麵還連接有節點的節點。
葉結點:自己下麵不再連接有節點的節點(即末端),稱為葉子節點(又稱為終端結點)。
B樹
所有的節點都可以存放完整的數據
MySQL的數據是存儲在磁碟文件中的,查詢數據時需要先把磁碟中的數據載入到記憶體中,磁碟IO操作非常耗時,所以我們優化的重點就是儘量減少磁碟IO操作,所以,我們應當儘量減少從磁碟中讀取數據的次數。另外,從磁碟中讀取數據時,都是按照磁碟塊來讀取的,並不是一條一條的讀。如果我們能把儘量多的數據放進磁碟塊中,那一次磁碟讀取操作就會讀取更多數據,那我們查找數據的時間也會大幅度降低。
如果我們用樹這種數據結構作為索引的數據結構,那我們每查找一次數據就需要從磁碟中讀取一個節點,也就是我們說的一個磁碟塊。我們都知道平衡二叉樹可是每個節點只存儲一個鍵值和數據的。那說明什麼?說明每個磁碟塊僅僅存儲一個鍵值和數據!那如果我們要存儲海量的數據呢?
可以想象到二叉樹的節點將會非常多,高度也會極其高,我們查找數據時也會進行很多次磁碟IO,我們查找數據的效率將會極低!
為瞭解決平衡二叉樹的這個弊端,B樹應運而生, B樹是一種多叉平衡查找樹,主要的特點是:
1、葉子節點都在同一層,葉子節點沒有指針連接
2、B樹的節點中存儲著多個元素,每個內節點有多個分叉
3、節點中的元素包含鍵值和數據,節點中的鍵值從大到小排列
4、所有的節點都可以存放完整的數據
下麵模擬下查找key為27的data的過程:
存在的一些問題:
B樹中每個節點中包含key值以及data值,而每一個節點的存儲空間是有限的(MySQL預設16K),如果data中存放的數據較大時,將會導致每個節點能存儲的key的數量很小,所以當數據量很多,且每行數據量很大的時候,同樣會導致樹的高度變得很高,增大查詢時的磁碟IO次數,進而影響查詢效率。
不支持範圍查詢的快速查找,而在實際的應用中,資料庫範圍查詢的頻率非常高,以下的一種情況是我查找10和35之間的數據,查找到15之後,需要回到根節點重新遍歷查找,需要從根節點進行多次遍歷,查詢效率有待提高。
B+\B*樹
只有葉子節點才會存放真正的數據 其他節點只存放索引數據
B+葉子節點增加了指向其他葉子節點的指針
B*葉子節點和枝節點都有指向其他節點的指針
對比B樹和B+樹,我們發現二者主要存在以下幾點不同的地方:
- 數據都存放在葉子節點中
- 非葉子節點只存儲鍵值信息,不再存儲數據
- 所有葉子節點之間都有一個指針,指向下一個葉子節點,而且葉子節點之間使用雙向指針連接,最底層的葉子節點形成了一個雙向有序鏈表
B+樹等值查詢
下麵模擬下查找key為9的data的過程:
B+樹範圍查詢
下麵模擬下查找key的範圍為9到26這個範圍的data的過程:
從上面的結果,我們可以知道B+樹作為索引結構帶來的好處:
- 磁碟IO次數更少
- 數據遍歷更為方便
- 查詢性能更穩定
由於B+樹優秀的結構特性,在MySQL中,存儲引擎MyISAM和InnoDB的索引就採用了B+樹的數據結構。
索引失效
有時候就算採用索引欄位查詢數據 也可能不會走索引!!!
1.前導模糊查詢不能利用索引(like '%XX'或者like '%XX%')
2.如果mysql估計使用全表掃描要比使用索引快,則不使用索引
3.OR前後存在非索引的列,索引失效
如果條件中有or,即使其中有條件帶索引也不會使用(這也是為什麼儘量少用or的原因)
要想使用or,又想讓索引生效,只能將or條件中的每個列都加上索引
4.普通索引的不等於不會走索引;如果是主鍵,則還是會走索引;如果是主鍵或索引是整數類型,則還是會走索引
5.計算導致索引失效
更多:
https://zhuanlan.zhihu.com/p/461253119
https://m.php.cn/article/487049.html
慢查詢優化
explain命令使用方法
mysql> explain select name,countrycode from city where id=1;
查詢數據的方式
全表掃描
1)在explain語句結果中type為ALL
2)什麼時候出現全表掃描?
2.1 業務確實要獲取所有數據
2.2 不走索引導致的全表掃描
2.2.1 沒索引
2.2.2 索引創建有問題
2.2.3 語句有問題
生產中,mysql在使用全表掃描時的性能是極其差的,所以MySQL儘量避免出現全表掃描
索引掃描
2.1 常見的索引掃描類型:
1)index
2)range
3)ref
4)eq_ref
5)const
6)system
7)null
從上到下,性能從最差到最好,我們認為至少要達到range級別
index
:Full Index Scan,index與ALL區別為index類型只遍歷索引樹。
range
:索引範圍掃描,對索引的掃描開始於某一點,返回匹配值域的行。顯而易見的索引範圍掃描是帶有between或者where子句裡帶有<,>查詢。
mysql> alter table city add index idx_city(population);
mysql> explain select * from city where population>30000000;
ref
:使用非唯一索引掃描或者唯一索引的首碼掃描,返回匹配某個單獨值的記錄行。
mysql> alter table city drop key idx_code;
mysql> explain select * from city where countrycode='chn';
mysql> explain select * from city where countrycode in ('CHN','USA');
mysql> explain select * from city where countrycode='CHN' union all select * from city where countrycode='USA';
eq_ref
:類似ref,區別就在使用的索引是唯一索引,對於每個索引鍵值,表中只有一條記錄匹配,簡單來說,就是多表連接中使用primary key或者 unique key 作為關聯條件A join B on A.sid=B.sid
const、system
:當MySQL對查詢某部分進行優化,並轉換為一個常量時,使用這些類型訪問。
如將主鍵置於where列表中,MySQL就能將該查詢轉換為一個常量
mysql> explain select * from city where id=1000;
NULL
:MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列里選取最小值可以通過單獨索引查找完成。
mysql> explain select * from city where id=1000000000000000000000000000;
參考:
https://www.cnblogs.com/Dominic-Ji/articles/15429493.html
https://www.cnblogs.com/Dominic-Ji/articles/15426531.html
資料庫設計三範式
第一範式:任何一張表都應該有自己的主鍵,並且每一個欄位的原子性都是不可再分的。
第二範式:在第一範式的基礎上,要求所有的非主鍵欄位完全依賴主鍵,不能產生部分依賴。
第三範式:在第二範式的基礎上,所有非主鍵只能依賴於主鍵,不能產生傳遞依賴。
'''
提醒:完美符合三範式的資料庫也比較少,為了滿足客戶的實際需求,
常常會用數據冗餘去換執行速度,就是魚和熊掌的關係。
'''