數據庫設計、查詢規範及常用SQL語句

1.數據庫設計規範

1.1 表設計

(1)表名前應加上前綴,表的前綴用系統或模塊的英文名稱縮寫;

(2)數據庫表名應該有意義,表名太長需要用前綴表示,並且易於理解,最好使用可以表達功能的英文單詞或縮寫;

(3)表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只出現數字;另外,表名不可以太長,最好不要超過3個英文單詞長度(22個字母);

數據庫字段名的修改代價很大,因爲無法進行預發佈,所以字段名稱需要慎重考慮。
說明:MySQL在Windows下不區分大小寫,但在Linux下默認是區分大小寫。
因此,數據庫名、表名、字段名,都不允許出現任何大寫字母,避免節外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name

(4)表名不使用複數名詞。在數據庫表命名時應該用英文單詞的單數形式,如員工表命名:應該爲Employee而不是Employees ;

說明:表名應該僅僅表示表裏面的實體內容,不應該表示實體數量;

(5)如果是後臺表命名時可以在表名基礎上加上後綴_b(backend首字母 );

(6)在表創建完成前,應該爲表及其各字段添加表的註釋;

(7)表必須定義主鍵,默認使用id字段,類型爲整型自增,如果不採用默認設計必須諮詢DBA進行設計評估;
(8)id字段作爲自增主鍵,禁止在非事務內作爲上下文的條件進行數據傳遞;
(9)在多個表中的相同列,必須保證列定義一致;
(10)國內mysql數據庫中表的引擎默認使用InnoDB,表字符集默認使用gbk;國際默認使用utf8字符集的表;其中,Innodb引擎提供了對數據庫ACID事務的支持,並且實現了SQL標準的四種隔離級別;
(11)表必須包含主鍵id、創建和修改者/時間字段,即表必須包含記錄創建時間(如用created_at表示)/創建者(可以使用creator)和修改時間(updated_at)/修改者(可以使用updator/editor)字段;

表必備三字段:id, created_at, updated_at。
說明:其中id必爲主鍵,類型爲unsigned bigint、單表時自增、步長爲1。
created_at, updated_at的類型均爲date_time類型。

(12)表示是否概念的字段,必須使用is_xxx的方式命名,數據類型使用unsigned tinyint(1表示是,0表示否)
說明:任何字段如果爲非負數,必須使用unsigned。正例:表達邏輯刪除的字段名is_deleted,1表示刪除,0表示未刪除。

(13)禁用保留字,如desc、range、match、delayed等,請參考MySQL官方保留字。

(14)主鍵索引名使用pk_前綴表示的字段名;唯一索引名爲uk_字段名;普通索引名則爲idx_字段名。
說明:pk_ 即primary key;uk_ 即unique key;idx_ 即index的簡稱。

(15)小數類型爲decimal,禁止使用float和double。
說明:float和double在存儲的時候,存在精度損失的問題,很可能在值的比較時,得到不正確的結果。
如果存儲的數據範圍超過decimal的範圍,建議將數據拆成整數和小數分開存儲。

(16)如果存儲的字符串長度幾乎相等,使用char定長字符串類型;varchar是可變長字符串,長度最好不要超過5000。
說明:varchar是可變長字符串,不預先分配存儲空間,長度不要超過5000,如果存儲長度大於此值,定義字段類型爲text,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效率。

(17)如果修改字段含義或對字段表示的狀態追加時,需要及時更新字段註釋。
(18)字段允許適當冗餘,以提高查詢性能,但必須考慮數據一致。
冗餘字段應遵循:

  • 不是頻繁修改的字段。
  • 不是varchar超長字段,更不能是text字段。
    正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中冗餘存儲類目名稱,避免關聯查詢。

(19)合適的字符存儲長度,不但節約數據庫表空間、節約索引存儲,更重要的是提升檢索速度。
正例:如下表,其中無符號值可以避免誤存負數,且擴大了表示範圍。
對象年齡區間類型字節表示範圍
人150歲之內unsigned tinyint(1)無符號值:0到255
龜數百歲unsigned smallint(2)無符號值:0到65535
恐龍化石數千萬年unsigned int(4)無符號值:0到約42.9億
太陽約50億年unsigned bigint(8)無符號值:0到約10的19次方

(20)單表數據量超過500w或數據容量超過10G考慮分庫分表,且需要提前考慮歷史數據遷移或使用腳本自行刪除歷史數據

說明:如果預計三年後的數據量根本達不到這個級別,請不要在創建表時就分庫分表。
(21)單條記錄大小禁止超過8k(列長度(中文)*2(gbk)/3(utf8)+列長度(英文)*1)
(22)日誌類數據不建議存儲在MySQL上,優先考慮Hbase

1.2 字段設計

  1. 表被索引列必須定義爲not null,並設置default值
    
  2. 禁止使用float、double類型,建議使用decimal或者int替代
    
  3. 禁止使用blob、text類型保留大文本、文件、圖片,建議使用其他方式存儲(TFS/SFS),MySQL只保存指針信息
    
  4. 禁止使用varchar類型作爲主鍵語句設計

1.3 語句設計

1)    數據更新建議使用二級索引先查詢出主鍵,再根據主鍵進行數據更新
2)    禁止使用非同類型的列進行等值查詢!

1.4 其他

  1. 禁止使用:存儲過程、觸發器、函數、視圖、事件等MySQL高級功能
    
  2. 禁止使用跨庫查詢
    
  3. 禁止使用子查詢,建議將子查詢轉換成關聯查詢
    
  4. 禁止核心業務流程SQL包含:計算操作、多表關聯、表遍歷case when等複雜查詢,建議拆分成單表簡單查詢
    
  5. varchar長度設計需要根據業務實際需要進行長度控制,禁止預留過長空間。例如status使用varchar(128)進行存儲

1.5 索引

1.5.1 索引原理

現在互聯網應用中對數據庫的使用多數都是讀較多,比例可以達到 10:1。並且數據庫在做查詢時 IO 消耗較大,所以如果能把一次查詢的 IO 次數控制在常量級那對數據庫的性能提升將是非常明顯的,因此基於 B+ Tree 的索引結構出現了。

B+ Tree 的數據結構
在這裏插入圖片描述
如圖所示是 B+ Tree 的數據結構,是由一個一個的磁盤塊組成的樹形結構,每個磁盤塊由數據項和指針組成。所有的數據都是存放在葉子節點,非葉子節點不存放數據。

查找過程:

以磁盤塊1爲例,指針 P1 表示小於17的磁盤塊,P2 表示在 17~35 之間的磁盤塊,P3 則表示大於35的磁盤塊。

比如要查找數據項99,首先將磁盤塊1 load 到內存中,發生 1 次 IO。接着通過二分查找發現 99 大於 35,所以找到了 P3 指針。通過P3 指針發生第二次 IO 將磁盤塊4加載到內存。再通過二分查找發現大於87,通過 P3 指針發生了第三次 IO 將磁盤塊11 加載到內存。最後再通過一次二分查找找到了數據項99。

由此可見,如果一個幾百萬的數據查詢只需要進行三次 IO 即可找到數據,那麼整個效率將是非常高的。

觀察樹的結構,發現查詢需要經歷幾次 IO 是由樹的高度來決定的,而樹的高度又由磁盤塊,數據項的大小決定的。磁盤塊越大,數據項越少那麼樹的高度就越低。這也就是爲什麼索引字段要儘可能小的原因。

1.5.2 幾個關鍵字的區別

key、primary key、unique key與index的區別:https://www.cnblogs.com/zjfjava/p/6922494.html

Mysql中key 、primary key 、unique key 與index區別:
https://www.cnblogs.com/zjfjava/p/6922494.html

索引被用來快速找出在一個列上用一特定值的行。沒有索引,MySQL不得不首先以第一條記錄開始並讀完整個表,直到它找出相關的行。

表越大,花費時間越多。如果表對於查詢的列有一個索引,MySQL能快速到達一個位置去搜尋到數據文件的中間,沒有必要考慮所有數據。

如果一個表有1000行,這比順序讀取至少快100倍。注意你需要存取幾乎所有1000行,它較快的順序讀取,因爲此時我們避免磁盤尋道。

所有的MySQL索引(PRIMARY、UNIQUE和INDEX)在B樹中存儲。字符串是自動地壓縮前綴和結尾空間。

1.5.3 索引使用場景

快速找出匹配一個WHERE子句的行;

當執行聯結時,從其他表檢索行;

對特定的索引列找出MAX()或MIN()值;

如果排序或分組在一個可用鍵的最左面前綴上進行(例如,ORDER BY key_part_1,key_part_2),排序或分組一個表。

如果所有鍵值部分跟隨DESC,鍵以倒序被讀取。

在一些情況中,一個查詢能被優化來檢索值,不用諮詢數據文件。

如果對某些表的所有使用的列是數字型的並且構成某些鍵的最左面前綴,爲了更快,值可以從索引樹被檢索出來。

2. 數據庫查詢

2.1 更新表中的數據和結構

2.1.1 UPDATE更新表中的數據

UPDATE customers

SET cust_name=‘The Fudds’ ,

cust_email=‘[email protected]

WHERE cust_id=10005;

2.1.2 ALTER TABLE——更改表的列結構(添加約束、索引等)

ALTER TABLE vendors

ADD vend_phone CHAR(20);

通過上面的更新表的語句,可看出,使用UPDATE語句是更新表的內容,UPDATE +表名 SET +要修改某些列的內容 +過濾條件;

使用ALTER TABLE是更新表的結構,ALTER TABLE +表名ADD +要添加的列數據類型/DROP COLUMN +要刪除的列名;

ALTER TABLE的一種常見用途是定義外鍵,如:

ALTER TABLE orderitems

ADD CONSTRAINT fk_orderitems_orders

FOREIGN KEY (order_num) REFERENCES orders (order_num);

2.2 刪除表中的數據與結構

2.2.1 DELETE FROM——刪除表中數據內容

DELETE FROM customers

WHERE cust_id = 10006;

DELETE FROM要求制定從中刪除數據的表明,WHERE子句過濾要刪除的行,如果省略WHERE子句,就會刪除表中的所有數據。注意,DELETE不需要列名或通配符,DELETE刪除的是整行而不是刪除列。爲了刪除指定的列,可以使用UPDATE語句。

2.2.2 DROP TABLE——刪除整個表結構(包括內容)

DROP TABLE customers2;

2.2.3 更快速地刪除表

如果想更快地從表中刪除所有的行,可以使用TRUNCATE TABLE語句,它與DELETE語句完成同樣的工作,但是速度更快,因爲TRUNCATE實際是刪除原來的表並重新創建一個表,而不是逐行刪除表中的數據。

RENAME TABLE——重命名錶

RENAME TABLE backup_customers TO customers,

backup_vendors TO vendors,

backup_products TO products;

通過RENAME TABLE語句可以重命名一張表,重命名多張表時中間使用逗號隔開,如上所示。

2.3 視圖VIEW的使用

視圖是虛擬的表,它們包含的不是數據而是根據需要檢索的查詢。

2.3.1 創建視圖——CREATE VIEW viewname

查看創建的視圖——SHOW CREATE VIEW viewname;

2.3.2 刪除視圖——DROP VIEW viewname;

更新視圖——方式一:先疏通DROP刪除視圖,在使用CREATE語句創建新的視圖;

方式二:直接使用CREATE OR REPLACE VIEW更新。如果要更新的視圖不存在,則該更新語句會創建一個視圖;如果要更新的視圖存在,怎會替換原有視圖。

2.3.3 使用視圖簡化複雜的聯結

視圖的最常見應用之一是隱藏複雜的SQL,這通常都會涉及聯結。從下面的實例可以看出,視圖極大地簡化了複雜SQL語句的使用,並且可以一次性編寫基礎的SQL,然後根據需要多次使用。這樣擴展視圖的適用範圍,不僅使得其能被重用,甚至更加有用。

CREATE VIEW productcustomers AS

SELECT cust_name, cust_contact, prod_id

FROM customers, orders, orderitems

WHERE customers.cust_id = orders.cust_id AND orderitems.order_num = orders.order_num;

此語句創建了一個名爲productcustomers的視圖,它聯結三個表,返回cust_name, cust_contact, prod_id客戶列表信息組成的視圖。然後,就可以通過如下的SELECT語句進行檢索:

SELECT cust_name, cust_contact

FROM productcustomers

WHERE prod_id = ‘TNT2’’;

2.3.4 使用視圖簡化字段

視圖對於簡化計算字段的使用非常有用,並且視圖非常容易創建,可以重複使用,正確使用視圖可極大地簡化複雜的數據處理。

CREATE VIEW orderitemsexpanded AS

SELECT order_num, prod_id, quantity, item_price, quantity*item_price AS expanded_price

FROM orderitems;

先通過上面的語句創建一個視圖,然後在視圖中檢索需要的字段,可簡化複雜數據的處理過程。

2.3.5 視圖能否更新

通常,視圖是可更新的,即可以對它們使用INSERT、UPDATE和DELETE。更新一個視圖將更新其基表,因爲視圖本身沒有數據。如果對視圖進行刪除、增加行,實際上是對其基表刪除或增加行。

但是,並非所有的視圖都是可更新的。如果MySQL不能正確地確定被更新的基數據,,則不允許更新(包括插入和刪除)。這意味着,如果視圖定義中有一下操作,則不能進行視圖的更新:

分組(使用GROUP BY和HAVING);聯結;子查詢;並;聚集函數(Min()、Count()、Sum()等);DISTINCT;導出(計算)列。

2.4 遊標

有時候,需要在檢索出來的行中前進或者後退一行或者多行,這就是使用遊標的原因。遊標(cursor)是一個存儲在MySQL服務器上的數據庫查詢,它不是一條SELECT語句,而是被該語句檢索出來的結果集。在存儲了遊標之後,應用程序可以根據需要滾動或瀏覽其中的數據。

遊標主要用於交互式應用,用戶需要滾動屏幕上的數據,並對數據進行瀏覽或者做出更改。不像DBMS,MySQL遊標只能用於存儲過程(和函數)。

2.5 觸發器

使用條件:在通過某條語句(或者某些語句)對某個表進行修改(插入、刪除、更新)時,自動進行處理,這就需要使用觸發器。只有表才支持觸發器,視圖不支持(臨時表也不支持)。

觸發器是MySQL響應DELETE、INSERT、UPDATE語句而自動執行的一條MySQL語句(或位於BEGIN和END語句之間的一組語句),其他MySQL語句不支持觸發器。

2.5.1 創建觸發器

創建觸發器時需要給出以下四條信息:

唯一的觸發器名字(觸發器名必須在每個表中唯一,但在 MySQL5中,同一個數據庫中的兩個表可以具有相同的觸發器名,這在其他DBMS中一般是不允許的,顧觸發器名最好在數據庫範圍內唯一);
觸發器關聯的表名稱;
觸發器應該響應的操作(DELETE、INSERT、UPDATE);
觸發器執行的時機(是在操作之前BEFORE還是之後AFTER)。
CREATE TRIGGER newproduct AFTER INSERT ON products

FOR EACH ROW SELECT ‘Product added’ ;

CREATE TRIGGER用來創建名爲newproduct的新觸發器,AFTER INSERT ON products表示此觸發器是在INSERT插入表products成功之後執行。這個觸發器還執行FOR EACH ROW ,表示代碼對每個插入行執行,後面的SELECT 'Product added’表示文本Product added將對每個插入的行顯示返回一次結果。

每個表每個事件每次只允許一個觸發器,因此,每個表最多支持6個觸發器(每條DELETE、INSERT、UPDATE之前或之後)。每個觸發器不能與多個事件或多個表關聯,所以,如果需要一個對INSERT和UPDATE操作執行的觸發器,則應該定義兩個觸發器。

2.5.2 刪除觸發器

刪除觸發器使用DROP TRIGGER語句,例如將上面創建的newproduct觸發器刪除:

DROP TRIGGER newproduct;

觸發器不能更新或覆蓋。爲了修改一個觸發器,必須先刪除它,然後在重新創建。

2.5.3 INSERT觸發器

INSERT觸發器在INSERT語句執行之前或者之後執行。需要注意以下幾點:

在INSERT觸發器代碼內,可引用一個名爲NEW的虛擬表,訪問被插入的行;
在BEFORE INSERT觸發器中,NEW中的值也可以被更新(允許更改被插入的值);
對於AUTO_INCREMENT列,NEW在INSERT執行之前包括0,在INSERT執行之後包含新的自動生成的值,其中AUTO_INCREMENT列具有MySQL自動賦予的值。
CREATE TRIGGER neworder AFTER INSERT ON orders

FOR EACH ROW SELECT NEW.order_num;

在成功執行下面的插入操作之後,將會出發上面的觸發器,並返回新的叮噹好order_num

INSER INTO orders(order_date, cust_id)

VALUSE(Now(), 10001);

orders包含3個列,order_date和cust_id必須給出,order_num有MySQL自動生成,而現在order_num會自動被返回。

2.5.4 DELETE觸發器

DELETE觸發器在DELETE語句執行之前或之後執行。需要注意以下幾點:

在DELETE觸發器代碼內,可以應用一個名爲OLD的虛擬表,訪問被刪除的行;
OLD中的值全都是隻讀的,不能更新。

CREATE TRIGGER deleteorder BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
INSERT INTO archive_orders(order_num, order_date, cust_id)
VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
END;

在任意訂單被刪除前將執行此觸發器。它使用一條INSERT語句將OLD(將要被刪除的訂單)中的值保存到一個名字爲archive)orders的存檔表中(在實際使用上面實例之前,需要用與orders相同的列創建一個名爲archive_orders的表)。

使用BEFORE DELETE觸發器的優點:相對於AFTER DELETE觸發器來說,如果因爲某種原因,訂單不能存檔,DELETE本身將被放棄(無效)。使用BEGIN END塊的好處是觸發器能容納一條或者多條SQL語句(在BEGIN END塊中一條挨着一條)。

2.5.5 UPDATE觸發器

UPDATE觸發器在UPDATE語句執行之前或者之後執行。需要注意以下幾點:

在UPDATE觸發器中,可以應用一個名爲OLD的虛擬表訪問UPDATE語句之前的值,引用一個名爲NEW的虛擬表訪問新更新的值;
在BEFORE UPDATE觸發器中,NEW中的值可能也被更新(即允許更改將要用於UPDATE語句中的值);
OLD中的值全都是隻讀的,不能更新。
CTEATE TRIGGER updatevendor BEFORER UPDATE ON vendors

FOR EACH ROW SET NEW.vend_state = Upper(NEW.vend_state);

該語句用來每次更新一行,NEW.vend_state中的值都用大寫的vend_state字符串替換。

2.6 注意事項

創建觸發器可能需要特殊的安全訪問權限,但是觸發器的執行是自動的。
應該使用觸發器來保證數據的一致性,包括大小寫、數據格式等。
觸發器的一種很重要的應用是穿件審計跟蹤,及使用觸發器把數據更改前後的狀態都備份到另一個表中,以備後續恢復使用。
MySQL觸發器中不支持CALL語句,即表示不能從觸發器內調用存儲過程。

3. SQL 優化

(1)反向查詢不能使用索引

select name from user where id not in (1,3,4);
應該修改爲:

select name from user where id in (2,5,6);
(2)前導模糊查詢不能使用索引

如:select name from user where name like ‘%zhangsan’

非前導則可以:select name from user where name like ‘zhangsan%’

建議可以考慮使用 Lucene 等全文索引工具來代替頻繁的模糊查詢。

(3)數據區分不明顯的不建議創建索引如:user 表中的性別字段;可以明顯區分的才建議創建索引,如身份證等字段。

(4)字段的默認值不要爲 null,這樣可能會帶來和預期不一致的查詢結果。

(5)使用limit可以提高效率。如果明確知道只有一條記錄返回,使用如下語句

select name from user where username=‘zhangsan’ limit 1
可以提高效率,讓數據庫停止遊標移動。

(6)在進行 join 連接時,要連接的兩個表的字段的類型要相同,不然也不會命中索引。

(7)不要讓數據庫幫我們做強制類型轉換

select name from user where telno=18722222222
這樣雖然可以查出數據,但是會導致全表掃描。

需要修改爲:

select name from user where telno=‘18722222222’
(8)在字段上進行計算不能命中索引

select name from user where FROM_UNIXTIME(create_time) < CURDATE();
應該修改爲:

select name from user where create_time < FROM_UNIXTIME(CURDATE());

4 參考

阿里巴巴MYSQL數據庫設計,查詢規範:

https://blog.csdn.net/qq_28296925/article/details/80455317

阿里的Mysql規範 MySQL庫表設計規範:

https://blog.csdn.net/u012966918/article/details/52161519

數據庫表及字段命名規範:

https://blog.csdn.net/yuzhouxiang/article/details/7088352

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章