mysql> show engines;
複製代碼
Engine | Support | Comment | Transactions | XA | Savepoints |
---|---|---|---|---|---|
InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
MyISAM | YES | MyISAM storage engine | NO | NO | NO |
CSV | YES | CSV storage engine | NO | NO | NO |
ARCHIVE | YES | Archive storage engine | NO | NO | NO |
PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL |
其餘引擎詳情請移步:dev.mysql.com/doc/refman/…html
InnoDB: The default storage engine in MySQL 5.7. InnoDB is a transaction-safe (ACID compliant) storage engine for MySQL that has commit, rollback, and crash-recovery capabilities to protect user data. InnoDB row-level locking (without escalation to coarser granularity locks) and Oracle-style consistent nonlocking reads increase multi-user concurrency and performance. InnoDB stores user data in clustered indexes to reduce I/O for common queries based on primary keys. To maintain data integrity, InnoDB also supports FOREIGN KEY referential-integrity constraints. For more information about InnoDB, see Chapter 14, The InnoDB Storage Engine.mysql
innodb: mysql 5.7 中的默認存儲引擎。innodb 是 mysql 的事務安全 (符合 acid) 存儲引擎, 具備提交、回滾和崩潰恢復功能, 可保護用戶數據。innodb 行級鎖定 (不升級到更粗粒度鎖) 和 oracle 類型一導致用非鎖定讀取,可提升多用戶併發性和性能。innodb 將用戶數據存儲在彙集索引中, 以減小基於主鍵的常見查詢的 I/O。爲了保持數據完整性, innodb 還支持外鍵。有關 innodb 的詳細信息, 請參閱第14章, inodb 存儲引擎。算法
Hash indexes have somewhat different characteristics from those just discussed:
They are used only for equality comparisons that use the = or <=> operators (but are very fast). They are not used for comparison operators such as < that find a range of values. Systems that rely on this type of single-value lookup are known as 「key-value stores」; to use MySQL for such applications, use hash indexes wherever possible.sql
哈希結構只適用於等值查詢(但這樣速度很是快)。哈希結構不支持順序檢索例如'<'、'>'、"between and"等,這種存儲結構屬於「鍵值」查詢,符合這種需求能夠考慮使用哈希索引。數據庫
The optimizer cannot use a hash index to speed up ORDER BY operations. (This type of index cannot be used to search for the next entry in order.)express
優化器不能使用哈希索引來加快 order by 操做。(此類型的索引不能用於按順序搜索下一個條目。數組
MySQL cannot determine approximately how many rows there are between two values (this is used by the range optimizer to decide which index to use). This may affect some queries if you change a MyISAM or InnoDB table to a hash-indexed MEMORY table.安全
mysql 不能大體肯定兩個值之間有多少行 (範圍優化器使用它來決定要使用哪一個索引)。若是將 MyISAM 或 InnoDB 表更改成哈希索引的內存表, 這可能會影響某些查詢。bash
只有等值匹配才能適用哈希結構查詢某一行。(而適用B-tree索引,最左前綴就能夠用於查詢。) 哈希表,優勢就是查詢快,缺點是範圍查詢效率很低(由於無序)。適用於等值查詢。數據結構
A B-tree index can be used for column comparisons in expressions that use the =, >, >=, <, <=, or BETWEEN operators. The index also can be used for LIKE comparisons if the argument to LIKE is a constant string that does not start with a wildcard character.
一個B樹索引能夠適用於=、>、>=、<、<=、BETWEEN 等操做符。B樹索引也能夠用於LIKE比較,只有當LIKE的參數是一個字符串常量而且不以通配符開始才能夠適用索引。 樹結構,優勢有序,而且多叉樹能夠減小磁盤I/O次數。
B-Tree中不管中間節點仍是葉子節點都帶有衛星數據。而B+Tree只有葉子節點帶有衛星數據,中間節點只帶有索引。以下圖所示: B-Tree的結構和B+Tree結構相似,只是非葉子節點也會存儲數據,而B+Tree只在葉子節點存儲數據,雖然B-Tree可能在遍歷到第二層時就能夠獲得數據返回,可是因爲非葉子節點也會存儲數據,致使每一個數據頁存儲的索引更少,致使樹的高度會很高,若是須要遍歷的數據在葉子節點,則很是費時,因此查詢性能不如B+Tree穩定。MySQL,InnoDB引擎一個數據頁大小爲16KB,因此從理論上講,一個數據頁存儲的有用信息越多,樹的高度就會越低,I/O次數越少,搜索效率越高。衛星數據:指索引元素所指向的數據記錄。例如數據庫中的某一行數據。
CREATE TABLE `r` (
`id` int NOT NULL primary key auto_increment,
`k` int not null,
`name` varchar(16),
index(k)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
複製代碼
在表中插入(1,10,"張三"),(2,20,"李四"),(3,30,"王五")。則索引以下圖
主鍵索引的葉子節點存的是整行數據,非主鍵索引的葉子節點存的主鍵的值。定義主鍵用主鍵做爲聚簇索引。
沒定義主鍵使用第一個惟一非空索引做爲聚簇索引。
沒定義主鍵,也沒定義惟一索引,生成一個隱藏的列做爲聚簇索引。
更多詳情
基於主鍵索引和普通索引查詢有什麼區別?
在分析一個sql語句:select * from r where k between 8 and 22;
這個例子因爲要查詢的結果只有主鍵索引上面纔有,因此不得不回表。那麼如何避免回表?
若是sql語句是:select id from r where k between 8 and 22,因爲這時只須要查詢id值,而id值已經在k索引樹上了,因此不須要回表查詢,索引k已經覆蓋了咱們的查詢需求,稱之爲覆蓋索引。
因爲覆蓋索引能夠減小數的搜索次數,顯著提升查詢性能,因此使用覆蓋索引是一個經常使用的優化手段。
場景:假設有一個市民表:
CREATE TABLE `citizen` (
`id` int(11) NOT NULL,
`id_card` varchar(32) DEFAULT NULL,
`name` varchar(32) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`ismale` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `id_card` (`id_card`),
KEY `name_age` (`name`,`age`)
) ENGINE=InnoDB
複製代碼
是否有必要建立身份證號和姓名的聯合索引?
根據業務來看,若是有根據身份證號查詢姓名的高頻需求,能夠考慮建立身份證號和姓名的聯合索引,避免回表提升查詢的效率。
select * from citizen where name = "張三" ;
複製代碼
這個確定是能夠用name索引的,若是要查詢姓張的人,語句是
select * from citizen where name like '張%';
複製代碼
這時也能夠用上name的索引,查找到第一個以張開頭的人,向後遍歷直到不知足條件爲止。
而若是要檢索姓張,年齡10歲的男孩。
select * from tuser where name like '張%' and age=10 and ismale=1;
複製代碼
這個在MySQL5.6之前是要根據查詢到姓張的人開始一個一個回表去查詢age是否知足10的,而5.6引入了索引下推優化(index condition pushdown),能夠在遍歷中,對索引中包含的字段先判斷,過濾掉不知足的記錄,減小回表次數。 如下兩句可使用到索引。
1. SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%';
2. SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%';
複製代碼
第一句只有'Patrick' <= key_col < 'Patricl'的行纔會被篩選出來,而第二句只有'Pat' <= key_col < 'Pau' 會被篩選出來。
如下兩句不會使用索引:
3. SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%';
4. SELECT * FROM tbl_name WHERE key_col LIKE other_col;
複製代碼
第三句因爲以通配符開始,不符合最左前綴原則,因此不能適用索引。第四句,因爲LIKE的參數不是一個字符串常量,因此也不使用索引。
若是用 LIKE '%string%' 字符串長度超過3,會使用串匹配的BM算法提升查詢效率。
另外,若是某一列有索引,若是值爲空,使用where col_name IS NULL也是能夠走索引的。
例如居民身份證號能夠保證惟一,那麼是否用身份證號當作主鍵建表?這裏並太建議,根據上面介紹的聚簇索引和二級索引的結構以後,能夠看出主鍵索引越長對於輔助索引創建須要更多的空間,另外對於聚簇索引,若是索引過長會致使主鍵索引樹的高度變高,由於一個數據頁默認是16k,主鍵索引越長則一個數據頁能容納的索引則越少。身份證號是18位,用字符串來存須要18個字節,而若是使用自增的long來作主鍵,則只有8個字節。另外一個好處就是自增主鍵能夠保證插入只須要插入到數據頁的隊尾,不須要插入中間,而身份證號按照順序排序有可能會插入中間位置,這樣會致使數據頁存滿,數據頁分裂等消耗。
場景一,根據郵箱登陸是一個廣泛場景,若是郵箱不加索引則須要權標掃描,而若是加入全量索引則須要佔用很大的空間。因爲字符串索引支持最左前綴原則,則咱們能夠這樣建立索引:
alter table user add index index(email(5));
複製代碼
這裏設置email的最左前5個字符做爲索引能夠縮小範圍,可是若是前5個字符可能重複的數據不少,好比zhangsan@XX.com、zhangsi@XX.com、zhangwu@XX.com、zhangliu@XX.com、zhangqi@XX.com都會搜索出來在遍歷,區別度過小,在某字段簡歷索引的一個原則就是這個字段的區別度,如此創建索引區別度過小。因此應該取得區別度可接受的最左前綴。
select count(distinct email) as L from user;(查詢總數)
複製代碼
而後執行下列語句,來看每一個前綴長度索引的區別度,找一個可以接受的長度,好比你的要求是區別度大於95%,那麼能夠算一下多長的前綴符合你的要求,區別度=L(n)/L。
select
count(distinct left(email,4) as L4,
count(distinct left(email,5) as L5,
count(distinct left(email,6) as L6,
count(distinct left(email,7) as L7,
from user;
複製代碼
select field_list from t where id_card = reverse('input_id_card_string');
複製代碼
alter table t add id_card_crc int unsigned, add index(id_card_crc);
複製代碼
查詢時候用如下語句:
select field_list from t where id_card_crc=crc32('input_id_card_string') and id_card='input_id_card_string');
複製代碼
這樣能夠先快速縮小結果集的範圍,在根據結果集遍從來查詢精確的身份證號,提升效率。
缺點:以上幾種方式都不支持範圍查詢,能夠本身根據業務場景本身選擇合適的方式。
讀未提交是指,一個事務還沒提交時,它作的變動就能被別的事務看到。
讀提交是指,一個事務提交以後,它作的變動纔會被其餘事務看到。
可重複讀是指,一個事務執行過程當中看到的數據,老是跟這個事務在啓動時看到的數據是一致的。
串行化,顧名思義是對於同一行記錄,「寫」會加「寫鎖」,"讀」會加「讀鎖」。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。
讀取到其餘事物未提交的結果。
在事物中無讀到其餘事物提交的UPDATE更新結果。
在事物中無讀到其餘事物提交的INSERT更新結果。
MySQL默認級別是可重複讀,Oracel默認級別是提交讀。
CREATE TABLE `r` (
`id` int NOT NULL primary key auto_increment,
`v` int not null
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into r values (1,1);
複製代碼
事物A | 事物B |
---|---|
begin; | |
1, select * from r; | begin; |
update r set v=v+1 where id =1; | |
commit; | |
2, select * from r; | |
3, select * from r for update; | |
commit; |
其中1,2,3句id等於3的列 v的值都等於多少?
update r set v = 1 where id =1;
複製代碼
事物A | 事物B |
---|---|
begin; | |
1, select * from r; | begin; |
update r set v=v+1 where id = 1; | |
commit; | |
2, select * from r; | |
update r set v=v+1 where id = 1; | |
3, select * from r; | |
commit; |
此時語句一、二、3查詢結果中v的值是多少?
簡單的select操做,屬於快照讀,不加鎖。
select * from table where ?;
複製代碼
特殊的讀操做,插入/更新/刪除操做,屬於當前讀,須要加鎖。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
複製代碼
全部以上的語句,都屬於當前讀,讀取記錄的最新版本。而且,讀取以後,還須要保證其餘併發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,其餘的操做,都加的是X鎖 (排它鎖)。
爲何併發讀不須要加鎖?
MVCC(參考高性能MySQL)
這裏簡單介紹一下MVCC(多版本併發控制),MVCC的實現是經過保存數據在某個時間點的快照來實現的。不一樣存儲引擎實現的方式也不一樣。這裏簡單介紹一下InnoDB簡化版行爲來講明MVCC是如何工做的。
InnoDB的MVCC是經過在每行記錄後面保存的兩個隱藏列來實現的。兩個列一個保存了行的建立時間,另個一保存了行的過時時間。這裏其實保存的並非具體時間,而是系統版本號(system version number)。每新開啓一個事物,系統版本號都會自動遞增,事物開始時刻的系統版本號會做爲事物的版本號,用來和查詢到的每行記錄版本做爲比較。
下面看下InnoDB, REPEATABLE READ隔離級別下MVCC是如何操做的。
a.查詢版本號早於或等於當前事物版本的行數據。這樣能夠保證讀取到的行,要麼早於該事物已經存在,要麼是本事物本身提交的。
b.行的刪除版本要麼未定義,要麼大於當前事物的版本號。這樣能夠保證,當前事物讀取到的行在事物開始以前是未被刪除的。
爲新插入的每一行數據保存當前系統版本號做爲行版本號。
爲刪除的每一行保存當前系統版本號做爲行刪除標誌。
a. 插入一行數據,保存當前系統版本號做爲行版本號。 b. 同時添加當前的系統版本號做爲原數據的刪除標記。
持有行記錄讀鎖的事物容許讀取該行記錄。
持有行記錄寫鎖的事物容許更新活刪除該行記錄。
A record lock is a lock on an index record. For example, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; prevents any other transaction from inserting, updating, or deleting rows where the value of t.c1 is 10.
行鎖是做用在索引記錄上的。例如, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;會防止其餘事物對於t.c1=10的增、刪、改。
Record locks always lock index records, even if a table is defined with no indexes. For such cases, InnoDB creates a hidden clustered index and uses this index for record locking.
行鎖老是鎖索引記錄的,儘管沒有建立索引,對於沒有建立索引的狀況,InnoDB建立一個隱藏的聚簇索引並用該索引來實現行鎖。
A gap lock is a lock on a gap between index records, or a lock on the gap before the first or after the last index record.
間隙鎖是做用於索引記錄之間或第一條索引記錄以前或最後一條索引記錄以後的鎖。
Gap locking is not needed for statements that lock rows using a unique index to search for a unique row.
間隙鎖不會做用於使用惟一索引去檢索惟一行記錄的狀況。
爲何會有間隙鎖?
防止幻讀。(當前讀)
delete from t where id = 10;
複製代碼
對id = 10的記錄加寫鎖 (走主鍵索引)。這個答案對嗎?
多是對的也有多是錯的,由於已知條件不足。缺乏下列條件:
1,id是不是主鍵?
2,id列若是不是主鍵,那麼id上是否有索引?
3,id列上若是有二級索引,那麼這個索引是不是惟一索引?
4,id=10的記錄是否存在?
根據如下組合來判斷加的鎖:
看下面幾個例子:
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`k` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_k` (`k`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
複製代碼
insert into t values(2,2),(6,6);
複製代碼
MySQL5.7版本,InnoDB引擎,隔離級別是RR狀況下,建立表t,其中id是主鍵,k是普通索引。
問題1:其中語句 一、二、3能夠插入嗎?
事物A | 事物B | 事物C | 事物D |
---|---|---|---|
begin; | |||
select * from t where k=4 for update; | |||
1, insert into t values (3,3); | |||
2, insert into t values (4,4); | |||
3, insert into t values (5,5); | |||
commit; |
是不能插入的,由於k是普通索引,因此會在索引 k 的B+樹上 k=2 至 k=6 之間加上間隙鎖,防止幻讀。
問題2:其中語句 四、五、六、 7能夠插入嗎?
事物A | 事物B | 事物C | 事物D | 事物E |
---|---|---|---|---|
begin; | ||||
select * from t where k=4 for update; | ||||
4, insert into t values (1,2); | ||||
5, insert into t values (3,2); | ||||
6, insert into t values (5,6); | ||||
7, insert into t values (7,6); | ||||
commit; |
其中語句 4, 7 是能夠插入的,而語句 5, 6 是不能插入的。
根據前面的加鎖機制分析一下緣由:
首先,select * from t where k=4 for update,因爲 k 是普通索引,因此在InnoDB引擎下,默認隔離級別RR狀況下會加間隙鎖防止新數據插入,防止幻讀。加鎖方式以下圖:
間隙鎖到底鎖的是什麼? 經過上面這個例子能夠看出,其實間隙鎖並無徹底鎖住k=2和k=6記錄的插入,也就是鎖住的不是具體的值,而是全部可能插入新值k=4的位置,在本例中也就是(2,2)至(6,6)之間的位置,由於索引是有序排列的,因此k=4只能插入(2,2)至(6,6)之間。
什麼狀況下產生間隙鎖?