1. 程式人生 > >深入淺出SQL(14)-約束、檢視與事務

深入淺出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';