一、事務
四大特性(ACID)
- 原子性
根據定義,原子性是指一個事務是一個不可分割的工作單位,其中的操作要么都做,要么都不做。即要么轉賬成功,要么轉賬失敗,是不存在中間的狀態! - 一致性
根據定義,一致性是指事務執行前后,數據處于一種合法的狀態,這種狀態是語義上的而不是語法上的。 - 隔離性
根據定義,隔離性是指多個事務并發執行的時候,事務內部的操作與其他事務是隔離的,并發執行的各個事務之間不能互相干擾。
4、持久性
根據定義,持久性是指事務一旦提交,它對數據庫的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
隔離級別引發的問題
- 臟讀
臟讀是指在一個事務處理過程里讀取了另一個未提交的事務中的數據。
當一個事務正在多次修改某個數據,而在這個事務中這多次的修改都還未提交,這時一個并發的事務來訪問該數據,就會造成兩個事務得到的數據不一致。
舉個例子,A在一個轉賬事務中,轉了100塊錢給B,此時B讀到了這個轉賬的數據,然后做了一些操作(發貨給A,或者其他的),可是這時候A的事務并沒有提交,如果A回滾了事務,那就GG了。這就是臟讀了。 - 不可重復讀
不可重復讀是指在對于數據庫中的某個數據,一個事務范圍內多次查詢卻返回了不同的數據值,這是由于在查詢間隔,被另一個事務修改并提交了。
例如事務T1在讀取某一數據,而事務T2立馬修改了這個數據并且提交事務給數據庫,事務T1再次讀取該數據就得到了不同的結果,發送了不可重復讀。
不可重復讀和臟讀的區別是,臟讀是某一事務讀取了另一個事務未提交的臟數據,而不可重復讀則是讀取了前一事務提交的數據。
在某些情況下,不可重復讀并不是問題,比如我們多次查詢某個數據當然以最后查詢得到的結果為主。但在另一些情況下就有可能發生問題,例如對于同一個數據A和B依次查詢就可能不同,A和B就可能打起來了…… - 幻讀
幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中所有的行的某個數據項做了從“1”修改為“2”的操作,這時事務T2又對這個表中插入了一行數據項,而這個數據項的數值還是為“1”并且提交給數據庫。而操作事務T1的用戶如果再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺一樣,這就是發生了幻讀。
幻讀和不可重復讀都是讀取了另一條已經提交的事務(這點就臟讀不同),所不同的是不可重復讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。
MySQL數據庫的四種隔離級別:
① Serializable (串行化):可避免臟讀、不可重復讀、幻讀的發生。
② Repeatable read (可重復讀):可避免臟讀、不可重復讀的發生。
③ Read committed (讀已提交):可避免臟讀的發生。
?、?Read uncommitted (讀未提交):最低級別,任何情況都無法保證。
以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似于Java多線程中的鎖)使得其他的線程只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL數據庫中默認的隔離級別為Repeatable read (可重復讀)。
在MySQL數據庫中,支持上面四種隔離級別,默認的為Repeatable read (可重復讀);而在Oracle數據庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認的為Read committed級別。
mysql 查詢事務的隔離級別
select @@tx_isolation;
在MySQL數據庫中設置事務的隔離 級別:
set [glogal | session] transaction isolation level 隔離級別名稱;
set tx_isolation=’隔離級別名稱;’
二、MySQL常見的三種存儲引擎(InnoDB、MyISAM、MEMORY)
InnoDB:支持事務處理,支持外鍵,支持崩潰修復能力和并發控制。如果需要對事務的完整性要求比較高(比如銀行),要求實現并發控制(比如售票),那選擇InnoDB有很大的優勢。如果需要頻繁的更新、刪除操作的數據庫,也可以選擇InnoDB,因為支持事務的提交(commit)和回滾(rollback)。
MyISAM:插入數據快,空間和內存使用比較低。如果表主要是用于插入新記錄和讀出記錄,那么選擇MyISAM能實現處理高效率。如果應用的完整性、并發性要求比 較低,也可以使用。
MEMORY:所有的數據都在內存中,數據的處理速度快,但是安全性不高。如果需要很快的讀寫速度,對數據的安全性要求較低,可以選擇MEMOEY。它對表的大小有要求,不能建立太大的表。所以,這類數據庫只使用在相對較小的數據庫表。
MyISAM與InnoDB特點
MyISAM特點
不支持行鎖(MyISAM只有表鎖),讀取時對需要讀到的所有表加鎖,寫入時則對表加排他鎖;
不支持事務
不支持外鍵
不支持崩潰后的安全恢復
在表有讀取查詢的同時,支持往表中插入新紀錄
支持BLOB和TEXT的前500個字符索引,支持全文索引
支持延遲更新索引,極大地提升了寫入性能
對于不會進行修改的表,支持 壓縮表 ,極大地減少了磁盤空間的占用
InnoDB特點
支持行鎖,采用MVCC(Mutil-Version Concurrency Control),就是多版本并發控制,來支持高并發,有可能死鎖
支持事務
支持外鍵
支持崩潰后的安全恢復
不支持全文索引
MyISAM和InnoDB兩者的應用場景:
- MyISAM管理非事務表。它提供高速存儲和檢索,以及全文搜索能力。如果應用中需要執行大量的SELECT查詢,那么MyISAM是更好的選擇。
- InnoDB用于事務處理應用程序,具有眾多特性,包括ACID事務支持。如果應用中需要執行大量的INSERT或UPDATE操作,則應該使用InnoDB,這樣可以提高多用戶并發操作的性能。
但是實際場景中,針對具體問題需要具體分析,一般而言可以遵循以下幾個問題:
- 數據庫是否有外鍵?
- 是否需要事務支持?
- 是否需要全文索引?
- 數據庫經常使用什么樣的查詢模式?在寫多讀少的應用中還是Innodb插入性能更穩定,在并發情況下也能基本,如果是對讀取速度要求比較快的應用還是選MyISAM。
- 數據庫的數據有多大? 大尺寸傾向于innodb,因為事務日志,故障恢復。
InnoDB和MyISAM區別
- InnoDB支持事務,MyISAM不支持,對于InnoDB每一條SQL語言都默認封裝成事務,自動提交,這樣會影響速度,所以最好把多條SQL語言放在begin和commit之間,組成一個事務;
- InnoDB支持外鍵,而MyISAM不支持。對一個包含外鍵的InnoDB表轉為MYISAM會失敗;
- InnoDB是聚集索引,使用B+Tree作為索引結構,數據文件是和(主鍵)索引綁在一起的(表數據文件本身就是按B+Tree組織的一個索引結構),必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然后再通過主鍵查詢到數據。因此,主鍵不應該過大,因為主鍵太大,其他索引也都會很大。
MyISAM是非聚集索引,也是使用B+Tree作為索引結構,索引和數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。
也就是說:InnoDB的B+樹主鍵索引的葉子節點就是數據文件,輔助索引的葉子節點是主鍵的值;而MyISAM的B+樹主鍵索引和輔助索引的葉子節點都是數據文件的地址指針。 - InnoDB不保存表的具體行數,執行select count(*) from table時需要全表掃描。而MyISAM用一個變量保存了整個表的行數,執行上述語句時只需要讀出該變量即可,速度很快(注意不能加有任何WHERE條件);
那么為什么InnoDB沒有了這個變量呢?
因為InnoDB的事務特性,在同一時刻表中的行數對于不同的事務而言是不一樣的,因此count統計會計算對于當前事務而言可以統計到的行數,而不是將總行數儲存起來方便快速查詢。InnoDB會嘗試遍歷一個盡可能小的索引除非優化器提示使用別的索引。如果二級索引不存在,InnoDB還會嘗試去遍歷其他聚簇索引。
如果索引并沒有完全處于InnoDB維護的緩沖區(Buffer Pool)中,count操作會比較費時??梢越⒁粋€記錄總行數的表并讓你的程序在INSERT/DELETE時更新對應的數據。和上面提到的問題一樣,如果此時存在多個事務的話這種方案也不太好用。如果得到大致的行數值已經足夠滿足需求可以嘗試SHOW TABLE STATUS - Innodb不支持全文索引,而MyISAM支持全文索引,在涉及全文索引領域的查詢效率上MyISAM速度更快高;PS:5.7以后的InnoDB支持全文索引了
- MyISAM表格可以被壓縮后進行查詢操作
- InnoDB支持表、行(默認)級鎖,而MyISAM支持表級鎖
InnoDB的行鎖是實現在索引上的,而不是鎖在物理行記錄上。潛臺詞是,如果訪問沒有命中索引,也無法使用行鎖,將要退化為表鎖。
8、InnoDB表必須有唯一索引(如主鍵)(用戶沒有指定的話會自己找/生產一個隱藏列Row_id來充當默認主鍵),而Myisam可以沒有
9、Innodb存儲文件有frm、ibd,而Myisam是frm、MYD、MYI
Innodb:frm是表定義文件,ibd是數據文件
Myisam:frm是表定義文件,myd是數據文件,myi是索引文件
如何選擇:
1. 是否要支持事務,如果要請選擇innodb,如果不需要可以考慮MyISAM;
2. 如果表中絕大多數都只是讀查詢,可以考慮MyISAM,如果既有讀也有寫,請使用InnoDB。
3. 系統奔潰后,MyISAM恢復起來更困難,能否接受;
4. MySQL5.5版本開始Innodb已經成為Mysql的默認引擎(之前是MyISAM),說明其優勢是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不會差。
InnoDB為什么推薦使用自增ID作為主鍵?
答:自增ID可以保證每次插入時B+索引是從右邊擴展的,可以避免B+樹和頻繁合并和分裂(對比使用UUID)。如果使用字符串主鍵和隨機主鍵,會使得數據隨機插入,效率比較差。
innodb引擎的4大特性
插入緩沖(insert buffer),二次寫(double write),自適應哈希索引(ahi),預讀(read ahead)
三、SQL執行順序
1、from 子句組裝來自不同數據源的數據;
2、where 子句基于指定的條件對記錄行進行篩選;
3、group by 子句將數據劃分為多個分組;
4、使用聚集函數進行計算;
5、使用 having 子句篩選分組;
6、計算所有的表達式;
7、select 的字段;
8、使用 order by 對結果集進行排序。
四、臨時表
MySQL用于存儲一些中間結果集的表,臨時表只在當前連接可見,當關閉連接時,Mysql會自動刪除表并釋放所有空間。為什么會產生臨時表:一般是由于復雜的SQL導致臨時表被大量創建。
內存臨時表空間的大小由兩個參數控制:tmp_table_size,max_heap_table_size。
下列操作會使用到內存臨時表:
union查詢
對于視圖的操作,比如使用一些TEMPTABLE算法、union或aggregation
子查詢
join 包括not in、exist等
查詢產生的派生表
復雜的group by 和 order by
Insert select 同一個表,mysql會產生一個臨時表緩存select的行
多個表更新
GROUP_CONCAT() 或者 COUNT(DISTINCT) 語句
Mysql還會阻止內存表空間的使用,直接使用磁盤臨時表:
表中含有BLOB或者TEXT列
使用union或者union all時,select子句有大于512字節的列
Show columns或者 desc 表的時候,有LOB或者TEXT
GROUP BY 或者 DISTINCT 子句中包含長度大于512字節的列
五. MySQL B+Tree索引和Hash索引的區別?
(1)Hash 索引僅僅能滿足"=","IN"和"<=>"查詢,不能使用范圍查詢。
由于 Hash 索引比較的是進行 Hash 運算之后的 Hash 值,所以它只能用于等值的過濾,不能用于基于范圍的過濾,因為經過相應的 Hash 算法處理之后的 Hash 值的大小關系,并不能保證和Hash運算前完全一樣。
(2)Hash 索引無法被用來避免數據的排序操作。
由于 Hash 索引中存放的是經過 Hash 計算之后的 Hash 值,而且Hash值的大小關系并不一定和 Hash 運算前的鍵值完全一樣,所以數據庫無法利用索引的數據來避免任何排序運算;
(3)Hash 索引不能利用部分索引鍵查詢。
對于組合索引,Hash 索引在計算 Hash 值的時候是組合索引鍵合并后再一起計算 Hash 值,而不是單獨計算 Hash 值,所以通過組合索引的前面一個或幾個索引鍵進行查詢的時候,Hash 索引也無法被利用。
(4)Hash 索引在任何時候都不能避免表掃描。
前面已經知道,Hash 索引是將索引鍵通過 Hash 運算之后,將 Hash運算結果的 Hash 值和所對應的行指針信息存放于一個 Hash 表中,由于不同索引鍵存在相同 Hash 值,所以即使取滿足某個 Hash 鍵值的數據的記錄條數,也無法從 Hash 索引中直接完成查詢,還是要通過訪問表中的實際數據進行相應的比較,并得到相應的結果。
(5)Hash 索引遇到大量Hash值相等的情況后性能并不一定就會比B-Tree索引高。
六、聚集索引和非聚集索引的概念區別
聚簇索引(主鍵索引)
1.使用記錄主鍵值的大小來進行記錄和頁的排序。
頁內的記錄是按照主鍵的大小順序排成一個單項鏈表。
各個存放用戶記錄的頁也是根據頁中用戶記錄的主鍵大小順序排成一個雙向鏈表。
2.葉子節點存儲的是完整的用戶記錄。
非聚簇索引(二級索引)
1.葉子節點內部使用name字段排序,葉子節點之間也是使用name字段排序。
2.葉子節點不再是完整的數據記錄,而是name和主鍵值。
區別:
1、聚集索引一個表只能有一個,而非聚集索引一個表可以存在多個
2、聚集索引存儲記錄是物理上連續存在,而非聚集索引是邏輯上的連續,物理存儲并不連續。
PS:
聯合索引
如果name和age組成一個聯合索引,那么先按name排序,如果name一樣,就按age排序。
索引使用原則
1.最左前綴原則。一個聯合索引(a,b,c),如果有一個查詢條件有a,有b,那么他則走索引,如果有一個查詢條件沒有a,那么他則不走索引。
2.使用唯一索引。具有多個重復值的列,其索引效果最差。例如,存放姓名的列具有不同值,很容易區分每行。而用來記錄性別的列,只含有“男”,“女”,不管搜索哪個值,都會得出大約一半的行,這樣的索引對性能的提升不夠高。
3.不要過度索引。每個額外的索引都要占用額外的磁盤空間,并降低寫操作的性能。在修改表的內容時,索引必須進行更新,有時可能需要重構,因此,索引越多,所花的時間越長。
4、索引列不能參與計算,保持列“干凈”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’);
5.一定要設置一個主鍵。前面聚簇索引說到如果不指定主鍵,InnoDB會自動為其指定主鍵,這個我們是看不見的。反正都要生成一個主鍵的,還不如我們設置,以后在某些搜索條件時還能用到主鍵的聚簇索引。
6.主鍵推薦用自增id,而不是uuid。上面的聚簇索引說到每頁數據都是排序的,并且頁之間也是排序的,如果是uuid,那么其肯定是隨機的,其可能從中間插入,導致頁的分裂,產生很多表碎片。如果是自增的,那么其有從小到大自增的,有順序,那么在插入的時候就添加到當前索引的后續位置。當一頁寫滿,就會自動開辟一個新的頁。
七、三范式
第一范式(1NF):是指在關系模型中,對域添加的一個規范要求,所有的域都應該是原子性的,即數據庫表的每一列都是不可分割的原子數據項,而不能是集合,數組,記錄等非原子數據項。即實體中的某個屬性有多個值時,必須拆分為不同的屬性。在符合第一范式(1NF)表中的每個域值只能是實體的一個屬性或一個屬性的一部分。簡而言之,第一范式就是無重復的域。
第二范式(2NF):在1NF的基礎上,非碼屬性必須完全依賴于候選碼(在1NF基礎上消除非主屬性對主碼的部分函數依賴),要求實體的屬性完全依賴于主關鍵字。所謂完全依賴是指不能存在僅依賴主關鍵字一部分的屬性,如果存在,那么這個屬性和主關鍵字的這一部分應該分離出來形成一個新的實體,新實體與原實體之間是一對多的關系。為實現區分通常需要為表加上一個列,以存儲各個實例的唯一標識。簡而言之,第二范式就是在第一范式的基礎上屬性完全依賴于主鍵。
第三范式(3NF):在2NF基礎上,任何非主屬性不依賴于其它非主屬性(在2NF基礎上消除傳遞依賴)。第三范式(3NF)是第二范式(2NF)的一個子集,即滿足第三范式(3NF)必須滿足第二范式(2NF)。簡而言之,第三范式(3NF)要求一個關系中不包含已在其它關系已包含的非主關鍵字信息。
八、SQL優化
為查詢緩存優化你的查詢,CURDATE()、 NOW() 、RAND() 無法生成緩存;
使用EXPLAIN,
當只要一行數據時使用 LIMIT 1
為搜索字段建索引
在Join表的時候使用相當類型的例,并將其索引
千萬不要 ORDER BY RAND()
避免 SELECT *
永遠為每張表設置一個ID,并且AUTO_INCREMENT
使用 ENUM 而不是 VARCHAR,當這些字段的取值是有限而且固定的
盡可能的使用 NOT NULL
Prepared Statements,抵抗SQL注入攻擊
把IP地址存成 UNSIGNED INT
無緩沖的查詢 :mysql_unbuffered_query()
固定長度的表會更快
垂直分割
用LIMIT拆分大的 DELETE 或 INSERT 語句,防止鎖表時間過長
越小的列會越快,可用 MEDIUMINT、SMALLINT、TINYINT來替代 INT 來做主鍵
使用一個對象關系映射器(Object Relational Mapper),善用“Lazy Loading”
小心“永久鏈接”