文/Bruce.Liu1
1.建模簡介
范式:英文名稱是 Normal Form,它是英國人 E.F.Codd(埃德加·弗蘭克·科德)在上個世紀70年代提出關系數據庫模型后總結出來的,范式是關系數據庫理論的基礎,也是我們在設計數據庫結構過程中所要遵循的規則和指導方法。目前有跡可尋的共有8種范式,依次是:1NF,2NF,3NF,BCNF,4NF,5NF,DKNF,6NF。通常所用到的只是前三個范式,即:第一范式(1NF),第二范式(2NF),第三范式(3NF)
埃德加·弗蘭克·科德(Edgar Frank Codd,1923-2003)是密執安大學哲學博士,IBM公司研究員,被譽為“關系數據庫之父”,并因為在數據庫管理系統的理論和實踐方面的杰出貢獻于1981年獲圖靈獎。1970年,科德發表題為“大型共享數據庫的關系模型”的論文,文中首次提出了數據庫的關系模型。由于關系模型簡單明了、具有堅實的數學理論基礎,所以一經推出就受到了學術界和產業界的高度重視和廣泛響應,并很快成為數據庫市場的主流。20世紀80年代以來,計算機廠商推出的數據庫管理系統幾乎都支持關系模型,數據庫領域當前的研究工作大都以關系模型為基礎。
數據庫建模:
在設計數據庫時,對現實世界進行分析、抽象、并從中找出內在聯系,進而確定數據庫的結構,這一過程就稱為數據庫建模。它主要包括兩部分內容:確定最基本的數據結構;對約束建模。
1.概念模型的表示方法
E-R圖主要是由實體、屬性和聯系三個要素構成的。在E-R圖中,使用了下面四種基本的圖形符號。2.確定系統實體、屬性及聯系
系統分析階段建立數據字典和數據流程圖->建立概念模型->邏輯模型->物理模型;利用系統分析階段建立的數據字典,并對照數據流程圖對系統中的各個數據項進行分類、組織,確定系統中的實體、實體的屬性、標識實體的碼以及實體之間聯系的類型。
在數據字典中“數據項”是基本數據單位,一般可以作為實體的屬性?!皵祿Y構”、“數據存儲”和“數據流”條目都可以作為實體,因為它們總是包含了若干的數據項。作為屬性必須是不可再分的數據項,也就是說在屬性中不能包含其他的屬性。3.確定局部(分)E-R圖
根據上面的分析,可以畫出部分實體-聯系圖。
在這些實體中有下畫線的屬性可以作為實體的碼,這幾個實體之間存在著1:1、l:n和m:n幾種聯系。-
4.集成完整(總)E-R圖
各個局部(分)E-R圖畫好以后,應當將它們合并起來集成為完整(總)E-R圖。在集成時應當注意如下幾點:- 消除不必要的冗余實體、屬性和聯系。
- 解決各分E-R圖之間的沖突。
- 根據情況修改或重構E-R圖。
2.三范式設計
什么是范式? 范式是“符合某一種級別的關系模式的集合,表示一個關系內部各屬性之間的聯系的合理化程度”。很晦澀吧?實際上你可以把它粗略地理解為一張數據表的表結構所符合的某種設計標準的級別。就像家里裝修買建材,最環保的是E0級,其次是E1級,還有E2級等等。數據庫范式也分為1NF,2NF,3NF,BCNF,4NF,5NF。一般在我們設計關系型數據庫的時候,最多考慮到BCNF就夠。符合高一級范式的設計,必定符合低一級范式,例如符合2NF的關系模式,必定符合1NF。
1.1.第一范式(1NF)
- 第一范式(1NF):屬性原子性約束
屬性是原子性的,即不可在拆分
考慮這樣一張表:【聯系人】(姓名,性別,電話) 。
如果在實際場景中,一個聯系人有家庭電話和公司電話,那么這種表結構設計就沒有達到 1NF。要符合 1NF 我們只需把列(電話)拆分,即:【聯系人】(姓名,性別,家庭電話,公司電話)
1.2.第二范式(1NF)
- 第二范式(2NF):主鍵約束
一是表必須有一個主鍵;二是沒有包含在主鍵中的列必須完全依賴于主鍵,而不能只依賴于主鍵的一部分。
考慮一個訂單明細表:【OrderDetail】(OrderID,ProductID,UnitPrice,Discount,Quantity,ProductName)。
因為我們知道在一個訂單中可以訂購多種產品,所以單單一個 OrderID 是不足以成為主鍵的,主鍵應該是(OrderID,ProductID)。顯而易見 Discount(折扣),Quantity(數量)完全依賴(取決)于主鍵(OderID,ProductID),而 UnitPrice(單價),ProductName 只依賴于 ProductID。所以 OrderDetail 表不符合 2NF。
可以把【OrderDetail】表拆分為【OrderDetail】(OrderID,ProductID,Discount,Quantity)和【Product】(ProductID,UnitPrice,ProductName)來消除原訂單表中UnitPrice,ProductName多次重復的情況。
1.3.第三范式(1NF)
- 第三范式(3NF):冗余性約束
任何字段不能由其他字段派生出來。要求字段沒有冗余。
考慮一個訂單表【Order】(OrderID,OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity)主鍵是(OrderID)。
其中 OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity 等非主鍵列都完全依賴于主鍵(OrderID),所以符合 2NF。不過問題是 CustomerName,CustomerAddr,CustomerCity 直接依賴的是 CustomerID(非主鍵列),而不是直接依賴于主鍵,它是通過傳遞才依賴于主鍵,多個訂單中有同一個用戶下單,就會產生數據冗余; 3NF中說的傳遞依賴,就出現了,所以不符合 3NF。
通過拆分【Order】為【Order】(OrderID,OrderDate,CustomerID)和【Customer】(CustomerID,CustomerName,CustomerAddr,CustomerCity)從而達到 3NF。
總結:通常來說,在數據庫建模設計中范式設計是指導思想,實際實踐中往往還要結合業務場景適當的進行調整。
3.反范式設計
denormalization?字面上就是做范式的反義詞,事實上也是。遵循范式總體上來說是為了保證數據的integrity和減少冗余,但是,從直覺上我們就可以知道,一個完全按照范式設計的冗余極低的數據庫,很可能在性能上會輸給冗余相對多一些的數據庫(比如說3NF的數據庫,表多,關系復雜,數據庫的IO次數很多,性能會收到影響)。增加冗余而提高性能,這便是denormalization的意義。至于說很多可能的異常,規避或者減輕的的手段有很多,是否遵循范式并不是唯一因素,denormalization也并不會導致出現異常。
3.1.三范式優缺點
三范式的優點:
- 減少冗余
- 減少異常(delete,update,insert)
- 讓數據組織的更加和諧(愚以為僅僅對于系統來說,對于人可完全不是這么回事)
三范式的缺點:
范式等級與復雜度是遞進的;通過范式的不斷升級,我們會發現應用的范式等級越高,則表越多。表多會帶來很多問題:
- 查詢時要連接多個表,增加了查詢的復雜度
- 查詢時需要連接多個表,降低了數據庫查詢性能
- 而現在的情況,磁盤空間成本基本可以忽略不計,所以數據冗余所造成的問題也并不是應用數據庫范式的理由。
3.2.反范式場景
在單庫中基本上都可以以三范式作為數據庫關系設計的核心思想,but在數據大爆炸的互聯網浪潮中,三范式就顯得力不從心。
- 數據庫水平拆分時,要通過數據冗余的方式減少join操作。
- 數據庫垂直拆分時,同樣三范式也滿足不了需求。
- 應用程序解耦時,需要數據庫層進行分離。
- 分布式數據庫場景中,如何匯總數據。
4.開發規范
4.1.表設計
- 庫名、表名、字段名使用小寫字母,”_”分割,不超過 12 個字符,使用名詞且見名知意
- 默認使用 innodb 存儲引擎,使用其他引擎必須注明緣由【FAQ】
- 存儲精確浮點數使用 DECIMAL,替代 FLOAT 和 DOUBLE
- 使用 int unsigned 存儲 IP 地址【FAQ】
- 根據字段長度選擇合適的字段類型,如數字類型有 tinyint,smallint,mediuint,int,bigint 五種類型,分別占用 1byte,2byte,3byte,4byte,8byte。需要特別注意,int(10)和 int(2)無區別,應該采用 tinyint(2)替代 int(2).
- 盡量使用 tinyint 代替 enum 和 set 類型,減少后臺類型轉換
- 盡量避免使用 text、blob 字段類型
- Varchar(N)中,N 表示的是字符數不是字節數,如 varchar(255),可以最大存儲255 個漢字。N 值應盡可能小,單表 varchar 字段最大長度為 65536 個字節,在排序和創建臨時表等內存操作時,會使用 N 值來申請內存,而非存儲值的實際長度
- 表字符集統一使用 utf8
- 存儲年使用 year 類型,存儲日期使用 date 類型,存儲時間(精確到秒)使用timestamp 類型,而非 datetime 類型。因為 timestamp 使用 4 個字節,而 datetime 使用 8 個字節【FAQ】.
- 字段全部定義為 NOT NULL【FAQ】
- 將過大字段拆分到其他表中,不在數據庫中存儲圖片、文件等內容
- 固定長度的表會更快【FAQ】
- 盡量避免使用外鍵【FAQ】
- 字段盡量全部定義為 NOT NULL:
通常情況下最好指定列為not null ,除非真的需要存儲NULL值。如果查詢中包含可為NULL的列,對于MySQL來說更難優化,因為可為NULL的列使得索引、索引統計和值比較都更復雜。可為NULL的列會使用更多的存儲空間,在MySQL里也需要特殊處理。當可為NULL的列被索引時,每個索引記錄需要一個額外的字節字節,在MyISAM里還可能導致固定大小的索引(只有一個int列的索引)變成可變大小的索引。
通常把已經上線的null改為not null帶來的性能提升比較小,所以調優時沒有必要優先糾正這種設計。但是如果計劃在列上建索引,就應該盡量把該列設計為not null
當然也有例外,指的一提的是,InnoDB使用單獨的位(bit)來存儲null值,所以對于稀疏數據(很多值為null,只有少數行為非null)的情況下有很好的空間效率。
int 類型可以定義為 not null default 0,
varchar 類型可以定義為 notnull default '' - 適當的拆分/冗余
A.當我們的表中存在類似于 TEXT 或者是很大的 VARCHAR類型的大字段的時候,如果我們大部分訪問這張表的時候都不需要這個字段,我們就該義無反顧的將其拆分到另外的獨立表中,以減少常用數據所占用的存儲空間。這樣做的一個明顯好處就是每個數據塊中可以存儲的數據條數可以大大增加,既減少物理 IO 次數,也能大大提高內存中的緩存命中率。
B.被頻繁引用且只能通過Join 2張(或者更多)大表的方式才能得到的獨立小字段,這樣的場景由于每次Join僅僅只是為了取得某個小字段的值,Join到的記錄又大,會造成大量不必要的IO,完全可以通過空間換取時間的方式來優化。不過,冗余的同時需要確保數據的一致性不會遭到破壞,確保更新的同時冗余字段也被更新。 - 控制表的大小
mysql在處理大表(char的表>500W行,或int表>1000W)時,性能就開始明顯降低,所以要采用不同的方式控制單表容量
- 根據數據冷熱,對數據分級存儲,歷史歸檔
- 采用分庫/分表/分區表,橫向拆分控制單表容量
- 對于OLTP系統,控制單事務的資源消耗,遇到大事務可以拆解,采用化整為零模式,避免特例影響大眾
- 單庫不要超過500個表
- 單表字段數不要太多,最多不要大于50個
4.2.索引設計
- 索引命名規范:
- 索引名稱全部使用小寫;
- 非唯一索引按照“ix_字段名稱字段名稱[字段名稱]”進行命名
- 唯一索引按照“uq_字段名稱字段名稱[字段名稱]”進行命名
- 唯一索引由 3 個以下字段組成并且字段都是整形時,使用唯一索引作為主鍵。沒有唯一索引
或唯一索引不符合上述條件時,使用自增 id 作為主鍵。注意唯一索引不和主鍵重復【FAQ】 - 單張表的索引數量控制在字段數的 20%以內,至多 5 個,索引數量過多會導致寫入性能 的顯著下降
- 合理創建復合索引。首先要避免冗余,ix_a_b_c 相當于同時創建了 ix-_a,ix_a_b,ix_a_b_c 三個索引;其次要避免索引過大,建議最大 4 列復合,列數過多很難提升索引的區分度,反而降低索引的性能
- 合理使用覆蓋索引
- 對于長度大于 100 的 varchar 字段建立索引時,使用其他方法【FAQ】
- 使用 EXPLAIN 判斷 SQL 語句是否合理使用索引,盡量避免 extra 列出現 FILE SORT,USINGTEMPORARY【FAQ】
- 索引不只用于 select 查詢,update 和 delete 語句也需要根據 where 條件合理設計索引
- where 條件中的非等值條件(IN,BETWEEN,<,<=,>,>=)會導致后面的條件無法使用索引
4.3.SQL設計
- 使用 prepared statement,可以提升性能并且避免 SQL 注入【FAQ】
- 降低 SQL 的復雜度,把 MySQL 盡量當做存儲使用:
- 避免在 SQL 語句中進行數學運算、函數計算、邏輯判斷等操作
- 避免多表 join,盡量拆分成多條查詢。如無法避免,在 join 表時應使用相同類型的列,并且在列上有索引【FAQ】
- 避免使用存儲過程、觸發器、函數等
- Insert 語句使用 batch 提交(insert into table values (),(),(),……),values 的個數不超過 500;sql 語句中 in 包含的值不超過 500
- Update,delete 語句避免使用 limit,如果確實需要分配處理大量數據,可以增加其他字段來限制每次處理的記錄數,比如主鍵 id
- 避免使用 select *【FAQ】
- 避免使用 order by rand(),使用其他方式替換【FAQ】
- 使用合理的分頁方式以提高分頁的效率
- 統計表中記錄數時使用 count(*),而不是 count(pk)或 count(1)
- 數據庫默認開啟查詢緩存,合理利用查詢緩存提升 sql 效率【FAQ】
- 當只需要 1 行數據時使用 limit 1【FAQ】
- 拆分大的 delete 和 insert【FAQ】
- Where 條件中使用合適的類型,數值不加引號,字符加引號,避免 MySQL 進行隱式類型轉換,從而無法使用索引【FAQ】
- 避免使用 or,對同一個字段將 or 改為 in,對不同字段將 or 改為 union【FAQ】
- 盡量避免負向查詢,如 NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE 等
- 針對同一張表的 alter table 操作,應該用逗號分隔,一次完成
- 注意 MySQL 中 insert ignore into;insert on duplicate key update;replace into 的區別,在應用中合理使用【FAQ】
4.4.分表設計
單表數據量控制在 300w 以下,如果字段全部為 int 類型,控制在 500w 以下
避免使用 MySQL 自帶的分區表功能,單表數據量時通過程序來分表,使用 hash 分表時,
表名后綴使用 16 進制表示,如 user_ff;使用日期分表時,表明后綴使用日期,如user_20130707 或 user_201307
4.5.其他
- 盡量減小事務:事務使用原則是即開即用,用完即關 ;事務無關操作放到事務外面, 減少鎖資源的占用;在不破壞一致性前提下,使用多個短事務代替長事務
- 可重復讀(repeatable read)是 MySQL 的默認事務隔離級別,解決了臟讀(dirty read)和幻讀(phantom read)的問題,原則上禁止修改事務的隔離級別?!綟AQ】
- 盡量使用短連接,完成查詢后要主動釋放連接,避免 MySQL 中出現大量 sleep 線程
- 不管使用連接池還是直連 MySQL,執行查詢前都需要考慮獲取的 MySQL 連接可能已經斷開,如斷開可以重連。執行查詢后都要檢查查詢是否成功,如不成功,考慮是否需要重
新執行。
4.6.注解
- FAQ1.2
從安全性和性能兩個角度都建議使用 InnoDB 引擎。首先數據量較大的時候,系統崩潰后如何快速恢復是一個重要問題。相對而言,MyISAM 崩潰后發生損壞的概率比 InnoDB 高
得多,而且恢復速度慢,可能丟失數據。因此,即使不需要支持事務,也推薦使用 innoDB.另外為改善 InnoDB 的性能,Oracle 投入了大量資源,對 Innodb 內部做了大量優化,使得
其性能在絕大部分場景遠高于 MyISAM。
- FAQ1.4
使用 INTUNSIGNED 而不是 char(15)來存儲 ipv4 地址,通過 MySQL 函數 inet_ntoa 和
inet_aton 來進行轉化。Ipv6 地址目前沒有轉化函數,需要使用 DECIMAL 或者兩個 bigINT 來存儲。
例如:
SELECT INET_ATON('209.207.224.40');
3520061480
SELECT INET_NTOA(3520061480);
209.207.224.40
FAQ1.10
INT[M],M值代表什么含義?
注意數值類型括號后面的數字只是表示寬度而跟存儲范圍沒有關系,比如INT(3)默認顯示3位,空格補齊,超出時正常顯示,python、java客戶端等不具備這個功能。
為什么建議使用TIMESTAMP來存儲時間而不是DATETIME?
DATETIME和TIMESTAMP都是精確到秒,優先選擇TIMESTAMP,因為TIMESTAMP只有4個字節,而DATETIME8個字節。同時TIMESTAMP具有自動賦值以及自動更新的特性。
如何使用TIMESTAMP的自動賦值屬性?
a) 將當前時間作為ts的默認值:ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP。
b) 當行更新時,更新ts的值:ts TIMESTAMP DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP。
c) 可以將1和2結合起來:ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- FAQ1.11
除非有很特別的原因去使用 NULL 值,最好總是讓字段保持 NOT NULL。
首先,確定“Empty”和“NULL”有多大的區別(如果是 INT,那就是 0 和 NULL)?如果覺得它們沒有什么區別,那么就不要使用 NULL。(在 Oracle 里,NULL 和 Empty 的字符串
是一樣的!)。NULL 的存儲需要額外的空間,并且,在進行比較的時候,會增加程序的復雜性。 當然,這里并不是說就不能使用 NULL 了,現實情況是很復雜的,依然會有些情況
下,你需要使用 NULL 值。
下面摘自 MySQL 官方文檔:
“NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”
- FAQ1.13
如果表中的所有字段都是“固定長度”的,整個表會被認為是 “static” 或 “fixed-length”。 例如,表中沒有如下類型的字段: VARCHAR,TEXT,BLOB。只要包括了其中一
個這些字段,那么這個表就不是“固定長度靜態表”了,這樣,MySQL 引擎會用另一種方法來處理。固定長度的表會提高性能,因為 MySQL 搜尋得會更快一些,因為這些固定的長度是很容易
計算下一個數據的偏移量的,所以讀取的自然也會很快。而如果字段不是定長的,那么,每一次要找下一條的話,需要程序找到主鍵。并且,固定長度的表也更容易被緩存和重建。不過,唯一的副作用是,固定長度的字段會浪費一些空間,因為定長的字段無論你用不用,他都是要分配那么多的空間。
- FAQ1.14
外鍵能夠簡化開發,但是外鍵在高并發下帶來嚴重的鎖問題,對性能影響極大,可以考慮通過程序來保證約束條件。
- FAQ2.2
建議使用自增 id 作為主鍵,以提升寫入性能。以寫入 10 萬行數據為例,對比如下:
主鍵字段
Int(10)auto_increment
Varchar(36):
快的方式。
寫入時間
133 秒
147 秒
小結:InnoDB 是聚集索引,寫入速度嚴重依賴于寫入順序,按照主鍵的順序寫入是最
- FAQ2.6
可以考慮使用前綴索引或模擬 hash 索引下面的表增加一列url_crc32,然后對url_crc32建立索引,減少索引字段的長度,提高效率。
CREATE TABLE url(
url VARCHAR(255) NOT NULL DEFAULT ‘’,
url_crc32 INT UNSIGNED NOT NULL DEFAULT 0,
……
index idx_url(url_crc32) )
- FAQ2.7
EXPLAIN 語句(在 MySQL 客戶端中執行)可以獲得 MySQL 如何執行 SELECT 語句的信息。通過對 SELECT 語句執行 EXPLAIN,可以知曉 MySQL 執行該 SELECT 語句時是否使用了索引、
全表掃描、臨時表、排序等信息。盡量避免 MySQL 進行全表掃描、使用臨時表、排序等。詳見官方文檔。
- FAQ3.1
Prepared Statements 很像存儲過程,是一種運行在后臺的 SQL 語句集合,我們可以從使用prepared statements 獲得很多好處,無論是性能問題還是安全問題。比如可以檢查一些你
綁定好的變量,這樣可以保護你的程序不會受到“SQL 注入式”攻擊。當然,你也可以手動地檢查你的這些變量,然而,手動的檢查容易出問題,而且很經常會被程序員忘了。當
我們使用一些 framework 或是 ORM 的時候,這樣的問題會好一些。在性能方面,當一個相同的查詢被使用多次的時候,這會為你帶來可觀的性能優勢。你可
以給這些 Prepared Statements 定義一些參數,而 MySQL 只會解析一次。雖然最新版本的 MySQL 在傳輸 Prepared Statements 是使用二進制形勢,所以這會使得網絡傳輸非常有效率。
- FAQ3.2.2
如果應用程序有很多 JOIN 查詢,應該確認兩個表中 Join 的字段是被建過索引的。這樣,MySQL 內部會啟動為你優化 Join 的 SQL 語句的機制。而且,這些被用來 Join 的字段,應該
是相同的類型的。例如:如果要把 DECIMAL 字段和一個 INT 字段 Join 在一起,MySQL 就無法使用它們的索引。對于那些 STRING 類型,還需要有相同的字符集才行。(兩個表的字符
集有可能不一樣)
// 在 state 中查找 company
$r = mysql_query("SELECT company_name FROM users LEFT JOIN companies ON (users.state = companies.state) WHERE users.id = $user_id");
// 兩個 state 字段應該是被建過索引的,而且應該是相當的類型,相同的字符集。
- FAQ3.5
從數據庫里讀出越多的數據,那么查詢就會變得越慢。并且還會增加網絡傳輸的負載。減少使用覆蓋索引完成查詢的可能性。所以,應該養成需要什么就取什么的好習慣。
// 不推薦
$r = mysql_query("SELECT * FROM user WHERE user_id = 1");
$d = mysql_fetch_assoc($r);
echo "Welcome {$d['username']}";
// 推薦
$r = mysql_query("SELECT username FROM user WHERE user_id = 1");
$d = mysql_fetch_assoc($r);
echo "Welcome {$d['username']}";
FAQ3.6
想打亂返回的數據行?隨機挑一個數據?方便用法的后面有非??膳碌男阅軉栴}。 如果真的想把返回的數據行打亂,有 N 種方法可以達到這個目。這樣使用會讓你的數據庫
性能呈指數級的下降,具體問題是:MySQL 會不得不去執行 RAND()函數(很耗 CPU 時間), 而且這是為了每一行記錄去記行,然后再對其排序。就算是你用了 Limit 1 也無濟于事(因
為要排序,很耗 IO)
下面的示例是隨機挑一條記錄
// 千萬不要這樣做:
$r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1");
// 這要會更好:
$r = mysql_query("SELECT count(*) FROM user");
$d = mysql_fetch_row($r);
$rand = mt_rand(0,$d[0] - 1);
$r = mysql_query("SELECT username FROM user LIMIT $rand, 1");
- FAQ3.9
某些查詢語句會讓 MySQL 不使用緩存。請看下面的示例:
// 不能夠使用緩存
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
// 能夠使用查詢緩存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
上面兩條 SQL 語句的差別就是 CURDATE() ,MySQL 的查詢緩存對這個函數不起作用。所以,像 NOW() 和 RAND() 或是其它的諸如此類的 SQL 函數都不會開啟查詢緩存,因為這些函數
的返回是會不定的易變的。所以,你所需要的就是用一個變量來代替 MySQL 的函數,從而使用緩存。
- FAQ3.10
當你查詢表的有些時候,你已經知道結果只會有一條結果, 在這種情況下,加上 LIMIT 1 可以增加性能。此時,MySQL 數據庫引擎會在找到一條數據后停止搜索,而不是繼續往后查少下一條符合記錄的數據。
下面的示例,只是為了找一下是否有“中國”的用戶,很明顯,后面的會比前面的更有效率。
// 沒有效率的:
$r = mysql_query("SELECT * FROM user WHERE country = 'China'");
if (mysql_num_rows($r) > 0) {
// ...
}
// 有效率的:
$r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1");
if (mysql_num_rows($r) > 0) {
// ...
}
- FAQ3.11
如果你需要在一個在線的網站上去執行一個大的 DELETE 或 INSERT 查詢,你需要非常小心, 要避免你的操作讓你的整個網站停止相應。因為這兩個操作是會鎖表的,表一鎖住了,別的操作都進不來了。
Apache 會有很多的子進程或線程。所以,其工作起來相當有效率,而我們的服務器也不希望有太多的子進程,線程和數據庫鏈接,這是極大的占服務器資源的事情,尤其是內存。
如果你把你的表鎖上一段時間,比如 30 秒鐘,那么對于一個有很高訪問量的站點來說,這 30 秒所積累的訪問進程/線程,數據庫鏈接,打開的文件數,可能不僅僅會讓你泊 WEB 服務 Crash,還可能會讓你的整臺服務器馬上掛了。
所以,如果你有一個大的處理,你定你一定把其拆分,使用 LIMIT 條件是一個好的方法。
下面是一個示例:
while (1) {
//每次只做 1000 條
mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
if (mysql_affected_rows() == 0) {
// 沒得可刪了,退出!
break;
}
// 每次都要休息一會兒
usleep(50000);
}
- FAQ3.12
因為 MySQL 進行隱式類型轉化之后,可能會將索引字段類型轉化成”=號”右邊值的類型, 導致使用不到索引.
- FAQ3.13
OR 的時間復雜度為 0(n),in 的時間復雜度為 0(log n),也就是說用 in 效率更高.
例子:
使用 in 提升性能
Select * from opp WHERE phone=‘12347856' or phone=‘42242233';修改為
Select * from opp WHERE phone in ('12347856' , '42242233')
Merge index 效果不好,使用 union all 提升性能。
例子:
Select * from opp WHERE phone='010-88886666' or cellPhone='13800138000'; ? 修改為
Select * from opp WHERE phone='010-88886666' union all Select * from opp WHERE
cellPhone='13800138000';
- FAQ3.16
1.insert ignore into
當插入數據時,如出現錯誤時,如重復數據,將不返回錯誤,只以警告形式返回。所以使 用 ignore 請確保語句本身沒有問題,否則也會被忽略掉。
2.on duplicate key update
當 primary key 或者 unique key 重復時,則執行 update 語句, 注意如果多個行匹配,只更新 1 行,所以應避免對帶有多個 unique key 的表使用 ON DUPLICATE KEY 子句
3.replace into
如果存在 primary key or unique key 相同的記錄,則先全部刪除掉,再插入新記錄。
- FAQ5.2
大多數數據庫系統的默認隔離級別是提交讀(READ COMMITTED),但 MySQL 是可重復讀 (repeatable read)。提交讀這個事務隔離級別也叫做不可重復讀。如果業務場景確實因為
大量的并發插入導致鎖問題嚴重,理論上可以通過降低隔離級別到提交讀,減少鎖爭用, 但是降級一方面會產生幻讀的問題,另一方面必須工作在 MySQL 的 binlog 格式是 row 的情
況下,否則會造成主從同步中斷。所以,從數據安全性角度出發,原則上禁止修改事務的隔離級別,性能問題盡量通過優化業務邏輯來解決。
FAQ5.3 MySQL中innodb表需要指定primary key?
.基于聚集索引的增、刪、改、查的效率相對是最高的;
.如果我們定義了主鍵(PRIMARY KEY),那么InnoDB會選擇其作為聚集索引;
.如果沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的唯一索引作為主鍵索引;
.如果也沒有這樣的唯一索引,則InnoDB會選擇內置6字節長的ROWID作為隱含的聚集索引(ROWID隨著行記錄的寫入而主鍵遞增,這個ROWID不像ORACLE的ROWID那樣可引用,是隱含的)。
綜上總結,如果InnoDB表的數據寫入順序能和B+樹索引的葉子節點順序一致的話,這時候存取效率是最高的,也就是下面這幾種情況的存取效率最高:
a.使用自增列(INT/BIGINT類型)做主鍵,這時候寫入順序是自增的,和B+數葉子節點分裂順序一致;
b.該表不指定自增列做主鍵,同時也沒有可以被選為主鍵的唯一索引(上面的條件),這時候InnoDB會選擇內置的ROWID作為主鍵,寫入順序和ROWID增長順序一致;
c.除此以外,如果一個InnoDB表又沒有顯示主鍵,又有可以被選擇為主鍵的唯一索引,但該唯一索引可能不是遞增關系時(例如字符串、UUID、多字段聯合唯一索引的情況),該表的存取效率就會比較差。
- 附錄
- 建模簡介
- 三范式設計
- 反范式設計
- 開發規范