深入淺出SQL(14)-約束、檢視與事務
約束、檢視與事務:人多手雜,資料庫受不了
資料庫到一定規模後,其他人也需要使用資料庫:
我們要防止其他人輸入錯誤的資料;
限制只看到部分資料的技術;
防止大家同時輸入資料時相互踩到別人的地盤;
本章,我們開始保護資料:“資料庫自衛術Part 1”;
本章使用的資料庫依然是mark_list:
各表中資料如下:
熟悉下準備好的資料,我們繼續;
現在作為表的管理者,隨著資料量的增加,一個人管理起來比較吃力,而且我還有其他的事情……
我們找了管理資料庫,拓展業務需求的幫手;
簡單來說,操作資料庫的人多了;
插入新的資料:
Maik MQ 14812121212 [email protected] ? 1994-01-01 teacher 海淀
我們看到,除了性別未知以外,其他資料都有,由於有了新的職業’教師’,相應的profession表也需要新增一條資料;
新的資料庫使用者user1,在插入資料的時候,儘量保證不出現NULL,於是他在性別的部分使用另一個字元X表示未知;
這裡回顧兩個知識點:
1.有AUTO_INCREMENT的列不需要放入值,表也會為我們插入主鍵列的值:
mysql> INSERT INTO profession -> VALUES -> (NULL,'student');
2.插入資料時,也可以使用子查詢:
mysql> INSERT INTO my_contacts -> (last_name,first_name,phone,email,gender,birthday,prof_id,zip_code) -> VALUES -> ('Mail','MQ','14912121212','[email protected]','X','1994-01-01',( -> SELECT id FROM profession -> WHERE profession = 'student'),'10002');
現在我們的聯絡人表如下:
伴隨著新值的插入,我們需要統計我們聯絡人中的男、女以及總人數:
查詢男、女的話加入WHERE條件即可;
mysql> SELECT COUNT(*)
-> FROM my_contacts;
不想要的’X’:
統計人數對不上,都是因為我們使用了這個字元;
我們該如何限制不讓user1輸入此類字元呢?
檢查約束:加入CHECK
約束:constraint 限定了可插入列的內容;他需要在我們建立表/更新表的時候加入約束;
我們之前見到的NOT NULL、PRIMARY KEY、FOREIGN KEY都是約束;
還有一種列約束稱為CHECK-檢查約束;
CHECK gender:
為my_contacts表的gender設定檢查約束;
約束限制輸入的字元只能是M或F;
mysql> ALTER TABLE my_contacts
-> ADD CONSTRAINT check_my_contacts CHECK (gender IN ('M','F'));
如果是在CREATE TABLE時加入約束的話,語法:
可以是在正常的列之後加上CHECK(something);
或者單獨一行CONSTRAINT check_my_contacts CHECK(something)
刪除檢查約束:DROP CHECK check_my_contacts;
很遺憾,mysql中的check並不好用,MYSQL的所有引擎均不支援check約束;
類似功能的可以通過enum型別或使用觸發器,或者在應用程式裡面對資料進行檢查再插入;
一個複雜的查詢:(我們就當他是一個複雜的吧)
查詢job_desired表中EngineerS的人的資訊;
mysql> SELECT mc.last_name last_name,mc.first_name first_name,mc.phone,mc.email
-> FROM my_contacts mc NATURAL JOIN job_desired jd
-> WHERE jd.title = 'EngineerS';
+-----------+------------+-------------+-------------+
| last_name | first_name | phone | email |
+-----------+------------+-------------+-------------+
| Mary | DM | 13212121212 | [email protected] |
+-----------+------------+-------------+-------------+
這個查詢並不難,但如果每天都有類似的查詢需求,我們是否能夠儲存查詢,每日看一下結果呢;
——我們可以把查詢變成檢視;
建立檢視:
只需在查詢中加入CREATE VIEW;
mysql> CREATE VIEW v_EngineerS AS
-> SELECT mc.last_name last_name,mc.first_name first_name,mc.phone,mc.email
-> FROM my_contacts mc NATURAL JOIN job_desired jd
-> WHERE jd.title = 'EngineerS';
檢視檢視:
把它想象成一張表,我們一樣可以SELECT它的內容;
mysql> SELECT * FROM v_EngineerS;
+-----------+------------+-------------+-------------+
| last_name | first_name | phone | email |
+-----------+------------+-------------+-------------+
| Mary | DM | 13212121212 | [email protected] |
+-----------+------------+-------------+-------------+
檢視的實際行動:
檢視的行為方式和子查詢一樣;
需要給子查詢一個別名,這樣查詢會把他當成一般的表,原因在於FROM子句需要表;
當SELECT語句的結果是一個虛擬表時,若沒有別名,SQL將無法取得其中的表;
mysql> SELECT * FROM (
-> SELECT mc.last_name last_name,mc.first_name first_name,mc.phone,mc.email
-> FROM my_contacts mc NATURAL JOIN job_desired jd
-> WHERE jd.title = 'EngineerS') AS engineerS_s;
+-----------+------------+-------------+-------------+
| last_name | first_name | phone | email |
+-----------+------------+-------------+-------------+
| Mary | DM | 13212121212 | [email protected] |
+-----------+------------+-------------+-------------+
何為檢視:
基本上,檢視是一個只有在查詢中使用VIEW才存在的表——被視為虛擬表(virtual table);
其行為和表一致,能執行表的操作;
和真正的虛擬表相比,真正的虛擬表不會一致儲存在資料庫裡;
檢視對資料庫的好處:
1.檢視把複雜的查詢簡化為一個命令;
檢視隱藏了查詢的複雜性;
2.即使一直改變資料庫的結構,也不會破壞依賴表的應用程式;
底層的資料表結果變化了,但可以以檢視模擬原始的表結構,這對舊的程式相容性很好;
3.建立檢視可以隱藏讀者無需看到的資訊;
我們可以使用檢視指向原始表,但又不必透露原始表的詳細資訊,保護敏感資訊不外洩;
實踐:找出聯絡人願意換工作的資訊,聯絡人資訊,舉例預期最低薪資的差距;
mysql> SELECT mc.last_name,mc.first_name,mc.email,jc.salary,jd.salary_low,(jc.salary - jd.salary_low) salary_raise
-> FROM my_contacts mc
-> INNER JOIN job_current jc
-> INNER JOIN job_desired jd
-> WHERE mc.contact_id = jc.contact_id AND mc.contact_id = jd.contact_id;
+-----------+------------+-------------+--------+------------+--------------+
| last_name | first_name | email | salary | salary_low | salary_raise |
+-----------+------------+-------------+--------+------------+--------------+
| Joy | HQ | [email protected] | 1000 | 800 | 200 |
| Mary | DM | [email protected] | 2000 | 1500 | 500 |
+-----------+------------+-------------+--------+------------+--------------+
如果我們在這個SQL前加上:CREATE VIEW v_salary_raise AS,那麼這一大段查詢就只需要輸入SELECT * FROM v_salary_raise;
利用檢視進行插入、更新與刪除:
SELECT、UPDATE、INSERT、DELETE均可,只要假裝檢視是真正的表:
語法同操作表一致,操作方式和原始表也是一致的,更新的資料需要滿足原始表的約束;
可以把檢視看作是原始表的門面;
但也不必要這麼麻煩:
如果檢視使用了統計函式,就無法用檢視改變資料;包含GROUP BY、DISTINCT、HAVING,也不行;
多數情況下,使用原始表的增、刪、改更容易;
我們新建一個my_contacts表的檢視:
mysql> CREATE VIEW v_contacts AS
-> SELECT contact_id,last_name, first_name, gender
-> FROM my_contacts
-> WHERE gender != 'X'
-> WITH CHECK OPTION;
Query OK, 0 rows affected (0.06 sec)
mysql> select * from v_contacts;
+------------+-----------+------------+--------+
| contact_id | last_name | first_name | gender |
+------------+-----------+------------+--------+
| 1 | Joy | HQ | M |
| 2 | Mary | DM | F |
| 4 | July | FM | F |
+------------+-----------+------------+--------+
插入一條資料:
錯誤1:我們需要把檢視當做一張真實的表,但這張表只有4列;
mysql> INSERT INTO v_contacts
-> (last_name,first_name,phone,email,gender,birthday,prof_id,zip_code)
-> VALUES
-> ('Lufu','LL','14012121213','[email protected]','X','1994-06-01',4,'10002');
ERROR 1054 (42S22): Unknown column 'phone' in 'field list'
錯誤2:檢視畢竟被視為虛擬表,操作的還是底層原始表,而原始表中 prof_id和zip_code是NOT NULL的;
mysql> INSERT INTO v_contacts
-> (last_name,first_name,gender)
-> VALUES
-> ('Lufu','LL','X');
ERROR 1423 (HY000): Field of view 'mark_list.v_contacts' underlying table doesn't have a default value
刪除掉檢視,我們重新建一個:
mysql> DROP VIEW v_contacts;
mysql> CREATE VIEW v_contacts AS
-> SELECT contact_id,last_name, first_name, gender,prof_id,zip_code
-> FROM my_contacts
-> WHERE gender != 'X'
-> WITH CHECK OPTION;
mysql> INSERT INTO v_contacts
-> (last_name,first_name,gender,prof_id,zip_code)
-> VALUES
-> ('David','DD','M',4,'10002');
查詢表資料:我們看到資料插入成功了;
CHECK OPTION的作用:
上邊的檢視新建時,我們使用了WITH CHECK OPTION,他的作用是要求RDBMS按照WHERE子句來判斷執行更新;
比如,我們針對上邊的檢視,插入如下的一條資料,就不會成功,因為檢視建立的WHERE子句要求不為X字元;
mysql> INSERT INTO v_contacts
-> (last_name,first_name,gender,prof_id,zip_code)
-> VALUES
-> ('Lufu','LL','X',4,'10002');
ERROR 1369 (HY000): CHECK OPTION failed 'mark_list.v_contacts'
但如果沒有建立檢視時新增CHECK OPTION的話,這條記錄就可以插入成功了;
由於MySQL不支援CHECK CONSTRAINT機制,可以使用CHECK OPTION模仿該功能;
因為檢視能夠精確的反應表的結構,還能強迫更新語句服從WHERE子句的條件;
可更新檢視:
類似上邊的v_contacts檢視,就是可更新檢視,他是可以改變底層表的檢視;
重點在於可更新檢視的內容需要包括它引用的表中所有設定為NOT NULL的列;
值得注意的是,你並不會經常使用檢視的INSERT、UPDATE、DELETE來更新表,因為直接操作表更容易;
使用完畢的檢視,請利用DROP VIEW語句清理空間;
值得注意的是:
檢視會像表一樣出現在資料庫中,SHOW TABLES和DESC可用來檢視檢視;
對於有檢視的表,如果不在需要了,最好先卸除檢視,然後再卸除它依據的表;
雖然檢查約束和檢視等對管理資料庫有幫助,也有助於維護控制權,但如果兩個人同時操作1列,就需要‘事務’的幫助了;
事務:
說到事務,還是要結合他最經典的場景,賬務餘額消費;
比如兩個人同時使用卡支付消費各100,而賬戶餘額只有150;
A、B在銷售員開出支付單之後繳費,此時都會校驗餘額,150>100,滿足條件,資料庫予以支出,但最終賬戶餘額成了-50;
在一個人支付成功之後,如果能再一次檢查餘額和消費金額,就能及時給出賬戶餘額不足的提示;
新建資料庫mark_amount,表my_amount:
並插入一條資料;
mysql> SELECT * FROM my_amount;
+----+------+--------+
| id | name | amount |
+----+------+--------+
| 1 | Mark | 150 |
+----+------+--------+
事務場景對應的SQL如下:
mysql> SELECT amount
-> FROM my_amount
-> WHERE name = 'Mark’;
mysql> SELECT amount
-> FROM my_amount
-> WHERE name = 'Mark’;
注:出問題的地方;
mysql> UPDATE my_amount
-> SET amount = (amount - 100)
-> WHERE name = 'Mark';
mysql> UPDATE my_amount
-> SET amount = (amount - 100)
-> WHERE name = 'Mark';
mysql> SELECT * FROM my_amount;
+----+------+--------+
| id | name | amount |
+----+------+--------+
| 1 | Mark | -50 |
+----+------+--------+
一開始150>100的檢查都通過了,但在消費之後,賬戶餘額竟成了負數;
如果SELECT和UPDATE能一起執行,分為兩組;
在組之間進行金額檢查,就可以避免這個問題了;
如果一系列SQL語句能以組的方式一起執行,而且在發生意外時SQL語句還能回到未執行的狀態——滿足這個功能就是事務;
事務(transaction):
是一群可完成一組工作的SQL語句;
上述過程就可以分解為兩個事務:
A: SELECT TO CHECK -> UPDATE AMOUNT;
B: SELECT TO CHECK -> UPDATE AMOUNT;
在事務過程中,如果所有步驟無法不受干擾地完成,則不該完成任何單一步驟;
ACIO構成事務的四個原則:
1)ATOMICITY:原子性
事務的每個步驟必須完成,否則只能都不完成;不能只執行部分事務;
2)CONSISTENCY:一致性
事務完成後應該維持資料庫的一致性,就是資料要對應的上;
3)ISOLATION:隔離性
表示每次事務都會看到具有一致性的資料庫,無論其他事務如何;新的事務無法影響正在執行的事務,直到其完成;
4)DURABILITY:永續性
資料庫需要正確地儲存資料並保護資料免受斷電或其他威脅的傷害;
通常把事務記錄在主資料庫以外的地方;
使用SQL管理事務:
有三種SQL事務管理工具可以保障賬戶的安全;
追蹤:START TRANSACTION;
持續追蹤後續所有SQL語句,直到輸入COMMIT或ROLLBACK為止;
提交:COMMIT;
提交所有程式碼造成的改變;
回滾:ROLLBACK;
逆轉改變過程,回到事務開始前狀態;
類似斷電的場景,可以在通電之後進行回滾;
值得注意的是,在你COMMIT前,資料庫都不會發生任何改變;
事務在MySQL下運作:
在SQL使用事務之前,你需要採用正確的儲存引擎(storage engine);
儲存引擎是儲存所有資料內容和結構的方式;
有些儲存引擎不允許事務;
儲存引擎必須是BDB或InnoDB,她他們都支援事務;
改變儲存引擎:
語法:ALTER TABLE table TYPE = InnoDB;
事務示例:
我們先把賬戶餘額更新為150;
然後使用事務進行查詢+更新;
1)初始:
mysql> SELECT amount
-> FROM my_amount
-> WHERE name = 'Mark';
+--------+
| amount |
+--------+
| 150 |
+--------+
2)事務:
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT amount
-> FROM my_amount
-> WHERE name = 'Mark';
+--------+
| amount |
+--------+
| 150 |
+--------+
1 row in set (0.00 sec)
mysql> UPDATE my_amount
-> SET amount = (amount - 100)
-> WHERE name = 'Mark';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT amount
-> FROM my_amount
-> WHERE name = 'Mark';
+--------+
| amount |
+--------+
| 50 |
+--------+
1 row in set (0.00 sec)
mysql> ROLLBACK;
Query OK, 0 rows affected (0.04 sec)
3)回滾之後:
mysql> SELECT amount
-> FROM my_amount
-> WHERE name = 'Mark';
+--------+
| amount |
+--------+
| 150 |
+--------+
1 row in set (0.00 sec)
第二次我們改用COMMIT,餘額資料再次查詢的值就是50了;
值得注意的是:
START TRANSACTION 和COMMIT與ROLLBACK必須搭配使用;
利用事務測試查詢是最合適的方法,這樣可以在資料庫裡實心影響資料的查詢,在出錯時還可以回滾;
RDBMS會記錄事務過程中的每個操作,稱為事務日誌(transaction log),操作越多,日誌越大;
事務最好留到真正需要的時候使用,避免儲存空間的浪費,也避免RDBMS花費太多精力追中每個操作;
在瞭解了限制資料操作的內容之後,下一章我們會介紹如何控制對錶的許可權;
總結:
本章主要介紹了檢視和事務;
1.VIEWS:檢視
使用檢視把查詢結果當成表;很合適簡化複雜查詢;
2.UPDATABLE VIEWS:可更新表
有些檢視能用於改變它底層的實際表;這類檢視必須包含底層表的所有NOT NULL列;
3.NON-UPDATABLE VIEWS:
無法對底層表執行INSERT或UPDATE操作的檢視;
4.CHECK CONSTRAINTS:檢查約束
可以只讓特定值插入或更新至表裡;
5.CHECK OPTION:
建立可更新檢視時,使用這個關鍵字可以強迫所有插入與更新的資料都滿足視圖裡的WHERE條件;
6.TRANSACTIONS:事務
一組必須同進退的查詢;如果這些查詢無法不受干擾的全部執行,則不承認其中部分查詢造成的改變;
7.START TRANSACTION:
告訴RDBMS開始事務;
在COMMIT之前的改變都不具有永久性,直到出現COMMIT和ROLLBACK;
ROLLBACK可以吧資料庫帶回START TRANSACTION前的狀態;
補充:
1.獲取全域性有關日誌的變數及值:
mysql> show global variables like '%log%';
2.檢視二進位制日誌:
mysql> show master logs;
或
mysql> show binary logs;
+---------------+-----------+
| Log_name | File_size |
+---------------+-----------+
| binlog.000001 | 628 |
| binlog.000002 | 33083 |
| binlog.000003 | 72477 |
| binlog.000004 | 5164 |
+---------------+-----------+
4 rows in set (0.00 sec)
mysql> show binlog events in 'binlog.000004';