MySQL(四)--------表型別(儲存引擎)的選擇
一、MySQL儲存引擎簡介
MySQL支援多種儲存引擎,以適用於不同領域的資料庫應用需要,使用者可以根據需要進行選擇甚至是定製自己的引擎以提高應用效率。
使用如下命令檢視當前版本mysql支援的儲存引擎:
mysql> show engines \G *************************** 1. row *************************** Engine: InnoDB Support: DEFAULT Comment: Supports transactions, row-level locking, and foreign keys Transactions: YES XA: YES Savepoints: YES *************************** 2. row *************************** Engine: MRG_MYISAM Support: YES Comment: Collection of identical MyISAM tables Transactions: NO XA: NO Savepoints: NO *************************** 3. row *************************** Engine: MEMORY Support: YES Comment: Hash based, stored in memory, useful for temporary tables Transactions: NO XA: NO Savepoints: NO *************************** 4. row *************************** Engine: BLACKHOLE Support: YES Comment: /dev/null storage engine (anything you write to it disappears) Transactions: NO XA: NO Savepoints: NO *************************** 5. row *************************** Engine: MyISAM Support: YES Comment: MyISAM storage engine Transactions: NO XA: NO Savepoints: NO *************************** 6. row *************************** Engine: CSV Support: YES Comment: CSV storage engine Transactions: NO XA: NO Savepoints: NO *************************** 7. row *************************** Engine: ARCHIVE Support: YES Comment: Archive storage engine Transactions: NO XA: NO Savepoints: NO *************************** 8. row *************************** Engine: PERFORMANCE_SCHEMA Support: YES Comment: Performance Schema Transactions: NO XA: NO Savepoints: NO *************************** 9. row *************************** Engine: FEDERATED Support: NO Comment: Federated MySQL storage engine Transactions: NULL XA: NULL Savepoints: NULL 9 rows in set (0.00 sec)
在使用MySQL建立新表的時候,如果沒有指定儲存引擎,那麼系統會使用預設的儲存引擎,MySQL5.5之前的預設儲存引擎是MyISAM,之後改成了InnoDB。使用如下命令檢視當前預設儲存引擎:
mysql> show variables like '%storage_engine%'; +----------------------------------+--------+ | Variable_name | Value | +----------------------------------+--------+ | default_storage_engine | InnoDB | | default_tmp_storage_engine | InnoDB | | disabled_storage_engines | | | internal_tmp_disk_storage_engine | InnoDB | +----------------------------------+--------+ 4 rows in set, 1 warning (0.00 sec)
在建立新表的時候如果不想用預設的儲存引擎,可以使用關鍵字ENGINE顯示的指定儲存引擎型別,下面ai表的儲存引擎定義為MyISAM,country表的引擎定義為InnoDB:
mysql> create table ai (i bigint(20) not null auto_increment,primary key (i) ) engine=MyISAM default charset=gbk; Query OK, 0 rows affected (0.01 sec) mysql> create table country (country_id smallint unsigned not null auto_increment,country varchar(50) not null, -> last_update timestamp not null default current_timestamp on update current_timestamp, -> primary key (country_id) ) -> engine=InnoDB default charset=gbk; Query OK, 0 rows affected (0.02 sec)
如果對於一個已經存在的表想要更改它的儲存引擎,可以使用alter語句:
mysql> alter table ai engine=innodb;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
查看錶的詳細資訊:
mysql> show create table ai \G;
*************************** 1. row ***************************
Table: ai
Create Table: CREATE TABLE `ai` (
`i` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk
1 row in set (0.00 sec)
定義好表的儲存引擎後就可以使用該引擎的相關特性。
二、各儲存引擎的特性
這裡主要介紹最常用的幾種引擎:
2.1 MyISAM
MyISAM不支援事務和外來鍵,其優勢是訪問速度快,所以對事務完整性沒有要求或者以SELECT、INSERT為主的應用基本上可以用這個引擎來建立表。
每個MyISAM型別的表在磁碟上都會儲存為3個檔案,檔名都和表名一樣,副檔名分別為:
- .frm (儲存表定義)
- .MYD (MYData,儲存資料)
- .MYI(MYIndex,儲存索引)
資料檔案和索引檔案可以放置在不同的目錄,平均分佈IO,獲得更快的速度;如果需要指定具體的目錄,在建立表的時候可以通過 DATA DIRECTORY 和 INDEX DIRECTORY 語句指定,但是需要注意,路徑必須是絕對路徑且具有訪問許可權。
MyISAM型別的表可能會損壞,造成的原因很多,損壞後會造成以下情況:表可能無法訪問;提示你需要修復;訪問後返回錯誤的結果;資料庫異常重新啟動等等。這時候我們就需要對錶進行修復,CHECK TABLE 語句來檢測MyISAM表的健康狀況,REPAIR TABLE 語句來修復。(具體修復過程及查詢損壞原因後面再介紹)
MyISAM表的3種不同儲存格式,分別是:
- 靜態表(固定長度);
- 動態表;
- 壓縮表;
關於這些表的具體介紹就不多說明,這跟資料結構裡面的一些儲存規則很類似,這裡僅僅說一下需要注意的一些點:
1. 在靜態表中查詢資料會在返回之前將後面的空格刪除:
mysql> create table myisam_char(name char(10)) engine=myisam;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into myisam_char values('abcde'),('abcde '),(' abcde'),(' abcde ');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> select name,length(name) from myisam_char;
+---------+--------------+
| name | length(name) |
+---------+--------------+
| abcde | 5 |
| abcde | 5 |
| abcde | 7 |
| abcde | 7 |
+---------+--------------+
4 rows in set (0.00 sec)
從上面的例子可以看出MyISAM型別的靜態表在儲存的時候會按照指定的列的寬度來向後補足空格,但是在應用訪問的時候會返回去掉後面空格的資料,因此在儲存的資料前後有空格時需要注意,資料後面的空格會被一併抹除掉,前面的空格則會保留。
2. 頻繁的更新和刪除動態表的記錄會產生碎片,因此需要定期執行OPTIMIZE TABLE 語句或 myisamchr -r 命令來改善效能。
3. 壓縮表由 myisampack 工具建立。
2.2 InnoDB
InnoDB是MySQL裡面預設的儲存引擎,也是使用的最多的儲存引擎;它提供了具有提交、回滾和崩潰恢復能力的事務安全效能,但相對於MyISAM儲存引擎,InnoDB寫的處理效率差一些,而且會佔用更多的磁碟空間以保留資料和索引。
1. 自增長列
InnoDB表的自增長列可以手動插入,如果忽略或是插入0或空值 ,則實際上該列還是會自動增長,看下面的例子:
mysql> create table autoincre_demo (i smallint not null auto_increment,name varchar(10),primary key(i))engine=innodb;
Query OK, 0 rows affected (0.02 sec)
mysql> insert into autoincre_demo values(1,'1'),(0,'2'),(null,'3');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from autoincre_demo;
+---+------+
| i | name |
+---+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+---+------+
3 rows in set (0.00 sec)
自增長列的起始值預設從1開始,如果想要從別的值開始可以用語句 ALTER TABLE *** AUTO_INCREMENT = n 來指定,但是需要注意,該語句強制指定的初始值是儲存在記憶體中的,一旦資料庫重新啟動就需要重新設定。
可以使用LAST_INSERT_ID()來查詢當前執行緒最後插入記錄使用的值:
mysql> insert into autoincre_demo values(4,'4');
Query OK, 1 row affected (0.00 sec)
mysql> select LAST_INSERT_ID(); 這裡需要解釋以下為什麼這裡查出來的記錄是2,因為這個查詢只會查到上一次的insert或update語句更改的最新記錄,但是它需要是自動增長的才算,手動指定的不算,比如這裡手動指定的記錄4就不算,而上面手動指定的0或null被自動增長代替,所以算作自增,因此此處結果為2.
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 2 |
+------------------+
1 row in set (0.00 sec)
mysql> insert into autoincre_demo(name) values('5'),('6'),('7'); 這裡沒有指定自增長列,它是自己增長的,因而查詢結果為當前多條資料的第一條自增長列對應的值,也就是5。
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 5 |
+------------------+
1 row in set (0.00 sec)
下面幾個例子就很好的證明了上面的結論
mysql> insert into autoincre_demo values(8,'8');
Query OK, 1 row affected (0.01 sec)
mysql> select LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 5 |
+------------------+
1 row in set (0.00 sec)
mysql> insert into autoincre_demo values(9,'9');
Query OK, 1 row affected (0.00 sec)
mysql> select LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 5 |
+------------------+
1 row in set (0.00 sec)
mysql> insert into autoincre_demo(name) values('10');
Query OK, 1 row affected (0.01 sec)
mysql> select LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 10 |
+------------------+
1 row in set (0.00 sec)
這裡必須提一點,對於InnoDB表,自增長列必須是索引,且排在第一列,如果是作為組合索引,它也必須組合索引的第一列。但是對於MyISAM表就不必,它的自增長列可以是組合索引的其他列,此時的自增就會先按照第一索引進行排序後再自增,如下面的例子:
mysql> create table autoincre_demo2(d1 smallint not null auto_increment,d2 smallint not null,name varchar(10),index(d2,d1))engine=myisam;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into autoincre_demo2(d2,name) values(2,'2'),(3,'3'),(4,'4'),(2,'2'),(3,'3'),(4,'4');
Query OK, 6 rows affected (0.00 sec)
Records: 6 Duplicates: 0 Warnings: 0
mysql> select * from autoincre_demo2;
+----+----+------+
| d1 | d2 | name |
+----+----+------+
| 1 | 2 | 2 |
| 1 | 3 | 3 |
| 1 | 4 | 4 |
| 2 | 2 | 2 |
| 2 | 3 | 3 |
| 2 | 4 | 4 |
+----+----+------+
6 rows in set (0.00 sec)
2. 外來鍵約束
MySQL中只有InnoDB支援外來鍵,在建立外來鍵時要求父表必須要有對應的索引。下面建立一個country表作為父表,city表作為子表,country_id作為外來鍵:
由於country表之前建立過,這裡就顯示一下它的結構
mysql> show create table country \G;
*************************** 1. row ***************************
Table: country
Create Table: CREATE TABLE `country` (
`country_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`country` varchar(50) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`country_id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk
1 row in set (0.00 sec)
mysql> create table city (city_id smallint unsigned not null auto_increment,
-> city varchar(50) not null,country_id smallint unsigned not null,
-> last_update timestamp not null default current_timestamp on update current_timestamp,
-> primary key(city_id),key idx_fk_country_id (country_id),
-> foreign key (country_id) references country (country_id) on delete restrict on update cascade)engine=innodb default charset=utf8;
Query OK, 0 rows affected (0.03 sec)
關鍵字RESTRICT和NO ACTION相同,作用是拒絕父表的的更新刪除操作(如果子表有關聯記錄的情況下);CASCADE表示父表在更新或刪除時,同時對子表對應的記錄進行同樣操作;SET NULL則表示父表在更新或刪除操作時,子表的對應欄位被置為空。後兩種要謹慎使用,因為會導致資料丟失。
因為外來鍵的存在導致表之間聯絡比較緊密,在匯入表資料或者修改表結構時往往由於這種聯絡導致速度較慢,此時我們可以暫時關閉外來鍵的檢查來加快處理速度,關閉的命令為:SET FOREIGN_KEY_CHECKS = 0;執行完之後通過命令:SET CHECK_FOREIGN_KEY = 1 ;改回原來的狀態。
對InnoDB型別的表,通過使用show table status命令來顯示包含外來鍵的表的狀態資訊:
mysql> show table status like 'city' \G;
*************************** 1. row ***************************
Name: city
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 0
Avg_row_length: 0
Data_length: 16384
Max_data_length: 0
Index_length: 16384
Data_free: 0
Auto_increment: 1
Create_time: 2018-12-17 16:07:17
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
3. 儲存方式
InnoDB儲存表和索引主要有兩種方式:
- 使用共享表空間儲存,資料和索引儲存在innodb_data_home_dir和innodb_data_file_path定義的表空間中,可以是多個檔案。
- 使用多表空間儲存,每個表的資料和索引單獨儲存在.ibd中。如果是分割槽表,則每個分割槽對應單獨的.ibd檔案,檔名時“表名+分割槽名”,可以在建立分割槽的時候指定每個分割槽的資料檔案的位置,以此將表的IO均勻的分佈在多個磁碟上。
一般都使用的共享表儲存,如果要使用多表空間儲存需要設定引數 innodb_file_per_table,並且需要重啟服務後才可以對新建的表生效,但原來的表仍使用共享表空間儲存;
多表空間的資料檔案沒有大小限制,不需要設定初始大小等一些其它限制引數;
使用多表空間特性的表,可以方便的進行單表備份和恢復操作,但不能直接複製.ibd檔案,因為還有一些東西在共享表空間裡,可以通過以下命令“ ALTER TABLE tb1_name DISCARD TABLESPACE; ALTER TABLE tb1_name IMPORT TABLESPACE;" 進行備份和恢復,但是隻能恢復到原來所在的資料庫中,如果想要恢復到別的資料庫需要通過mysqldump和mysqlimport來實現。
注意:即使在多表空間的儲存方式下,共享表空間仍然是必須的,因為InnoDB把內部資料詞典和線上重做日誌放在這個檔案中。
2.3 MEMORY
MEMORY儲存引擎使用記憶體中的內容來建立表,每個MEMORY表只實際對應一個磁碟檔案,格式是.frm。它的訪問速度很快,預設採用hash索引,但一旦服務關閉,表中的資料就會丟失。
下面使用city表的記錄來建立memory的表,並且指定索引為hash索引還是btree索引:
mysql> create table tab_memory engine=memory select city_id,city,country_id from city group by city_id;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index men_hash using hash on tab_memory (city_id);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from tab_memory \G;
*************************** 1. row ***************************
Table: tab_memory
Non_unique: 1
Key_name: men_hash
Seq_in_index: 1
Column_name: city_id
Collation: NULL
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null:
Index_type: HASH
Comment:
Index_comment:
1 row in set (0.00 sec)
如果想把hash索引更換為btree索引,需這樣做:
mysql> drop index men_hash on tab_memory;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index men_hash using btree on tab_memory(city_id);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
如何將memory型別表的資料儲存下來?我們在啟動MySQL時需要使用--init-file選項,把把各種表單資料操作語句放入到檔案中,這樣就可以在在服務啟動時從持久穩固的資料來源裝載表。
memory表儲存資料的大小受到max_heap_table_size系統變數的約束,初始值為16MB,當然,我們可以根據需要加大,並且在建立memory表時可以通過MAX_ROWS字句指定表的最大行數來限制表的大小。
當memory表不再用的時候需要釋放它的記憶體,應當執行DELETE FROM 或TRUNCATE TABLE 或 DROP TABLE操作。
memory型別的表主要用於那些內容變化不頻繁的程式碼表,或者作為統計的中間結果表;對這種表進行更新操作需要謹慎,應考慮到資料的永續性問題。
2.4 MERGE
MERGE儲存引擎是一組MyISAM表的組合,只不過要求MyISAM表的結構必須完全相同,MERGE表本身在磁碟上只有兩個檔案,一個.frm檔案儲存表結構,另一個.MRG檔案包含組合表的資訊。
對MERGE表一些資料上從操作實際上是對內部的MyISAM表進行的,比如插入操作:用INSERT_METHOD子句,它有三個取值,FIRST或LAST表明是插入內部第一個還是最後一個表,NO或者不定義這個子句表示不能執行插入操作。比如DROP操作:只刪除MERGE表的定義,對內部的表沒有任何影響。
如果通過.MRG檔案來修改MERGE表,那需要 FLUSH TABLES 來重新整理後才可生效。
下面舉個例子:用兩個myisam表建立merge表
mysql> create table payment_2006(country_id smallint,payment_date datetime,amount decimal(15,2), key idx_fk_country_id(country_id))engine=myisam;
Query OK, 0 rows affected (0.01 sec)
mysql> create table payment_2007(country_id smallint,payment_date datetime,amount decimal(15,2), key idx_fk_country_id(country_id))engine=myisam;
Query OK, 0 rows affected (0.01 sec)
mysql> create table payment_all(country_id smallint,payment_date datetime,amount decimal(15,2), index(country_id))engine=merge union=(payment_2006,payment_2007) insert_method=last;
Query OK, 0 rows affected (0.01 sec)
向myisam表中插入資料:
mysql> insert into payment_2006 values(1,'2006-05-01',100000),(2,'2006-08-15',150000);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> insert into payment_2007 values(1,'2007-05-01',350000),(2,'2007-08-15',220000);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
分別檢視merge表和myisam表中資料:
mysql> select * from payment_2006;
+------------+---------------------+-----------+
| country_id | payment_date | amount |
+------------+---------------------+-----------+
| 1 | 2006-05-01 00:00:00 | 100000.00 |
| 2 | 2006-08-15 00:00:00 | 150000.00 |
+------------+---------------------+-----------+
2 rows in set (0.00 sec)
mysql> select * from payment_2007;
+------------+---------------------+-----------+
| country_id | payment_date | amount |
+------------+---------------------+-----------+
| 1 | 2007-05-01 00:00:00 | 350000.00 |
| 2 | 2007-08-15 00:00:00 | 220000.00 |
+------------+---------------------+-----------+
2 rows in set (0.00 sec)
mysql> select * from payment_all; 可以看出merge表是myisam表的集合。
+------------+---------------------+-----------+
| country_id | payment_date | amount |
+------------+---------------------+-----------+
| 1 | 2006-05-01 00:00:00 | 100000.00 |
| 2 | 2006-08-15 00:00:00 | 150000.00 |
| 1 | 2007-05-01 00:00:00 | 350000.00 |
| 2 | 2007-08-15 00:00:00 | 220000.00 |
+------------+---------------------+-----------+
4 rows in set (0.00 sec)
下面向merge表插入一條資料:
mysql> insert into payment_all values(3,'2006-3-21',112200);
Query OK, 1 row affected (0.00 sec)
mysql> select * from payment_all;
+------------+---------------------+-----------+
| country_id | payment_date | amount |
+------------+---------------------+-----------+
| 1 | 2006-05-01 00:00:00 | 100000.00 |
| 2 | 2006-08-15 00:00:00 | 150000.00 |
| 1 | 2007-05-01 00:00:00 | 350000.00 |
| 2 | 2007-08-15 00:00:00 | 220000.00 |
| 3 | 2006-03-21 00:00:00 | 112200.00 |
+------------+---------------------+-----------+
5 rows in set (0.00 sec)
mysql> select * from payment_2007;
+------------+---------------------+-----------+
| country_id | payment_date | amount |
+------------+---------------------+-----------+
| 1 | 2007-05-01 00:00:00 | 350000.00 |
| 2 | 2007-08-15 00:00:00 | 220000.00 |
| 3 | 2006-03-21 00:00:00 | 112200.00 |
+------------+---------------------+-----------+
3 rows in set (0.00 sec)
可以看到資料插在了後一個表中,這是由於之前定義的插入方式為last導致的。
2.5 TokuDB(第三方儲存引擎)
TokuDB是一個高效能、支援事務處理的MySQL和MariaDB的儲存引擎,具有高擴充套件性、高壓縮率、高效的寫入效能,支援大多數線上DDL操作,其它相關內容可以在其官網(http://www.tokutek.com)進行檢視。
根據其效能,我們發現它特別適合以下幾種場景:
- 日誌資料,因為日誌資料通常插入頻繁且儲存量大;
- 歷史資料,通常不會有寫操作,可用其高壓縮性進行儲存;
- 線上DDL較頻繁的場景,該引擎增加系統的可用性。
三、如何選擇合適的儲存引擎
根據合適的情況選擇相應的引擎,有時候還可以選用多種引擎進行組合。
- MyISAM:如果應用是以讀操作和插入操作為主,只有很少的更新和刪除操作,並且對事務的完整性、併發性要求不高,那麼該引擎很合適。常用在web、資料倉庫等環境下;
- InnoDB:如果事務的完整性要求較高,在併發條件下要求資料的一致性,資料操作增刪改查都很多,那麼該引擎較合適。常用在計費系統、財務系統等對資料準確性要求比較高的系統中;
- MEMORY:資料儲存在RAM中,訪問速度快,有大小限制,通常用於更新不太頻繁的小表或作為中間錶快速得到訪問結果;
- MERGE:用於將一系列等同的MyISAM表以邏輯方式組合在一起,並作為一個物件引用他們。可以突破對單個MyISAM表的大小限制,並且通過將不同的表分佈在多個磁碟上可以有效改善MERGE表的訪問效率。對諸如資料倉庫等VLDB環境很適合。