mysql從頭學一 1.1儲存引擎 MyISAM和 innoDB
各種儲存引擎的特性
下面重點介紹幾種常用的儲存引擎,並對比各個儲存引擎之間的區別,以幫助讀者理解
不同儲存引擎的使用方式。
表7-1 常用儲存引擎的對比
特點 |
MyISAM |
InnoDB |
MEMORY | MERGE |
NDB |
儲存限制 |
有 |
64TB |
有 |
沒有 |
有 |
事務安全 |
|
支援 |
|
|
|
鎖機制 |
表鎖 |
行鎖 |
表鎖 |
表鎖 |
行鎖 |
B 樹索引 |
支援 |
支援 |
支援 |
支援 |
支援 |
雜湊索引 |
|
|
支援 |
|
支援 |
全文索引 |
支援 |
|
|
|
|
叢集索引 |
|
支援 |
|
|
|
資料快取 |
|
支援 |
支援 |
|
支援 |
索引快取 |
支援 |
支援 |
支援 |
支援 |
支援 |
資料可壓縮 |
支援 |
|
|
|
|
空間使用 |
低 |
高 |
N/A |
低 |
低 |
記憶體使用 |
低 |
高 |
中等 |
低 |
高 |
批量插入的速度 |
高 |
低 |
高 |
高 |
高 |
支援外來鍵 |
|
支援 |
|
|
|
下面將重點介紹最常使用的 4 種儲存引擎:MyISAM、InnoDB、MEMORY 和 MERGE。NDB
儲存引擎會在第 33 章 MySQL CLUSTER 中詳細介紹,這裡不再贅述。
7.2.1 MyISAM
MyISAM 是 MySQL 的預設儲存引擎。MyISAM 不支援事務、也不支援外來鍵,其優勢是訪問的速度快,對事務完整性沒有要求或者以 SELECT、INSERT 為主的應用基本上都可以使用這個引擎來建立表。
每個 MyISAM 在磁碟上儲存成 3 個檔案,其檔名都和表名相同,但副檔名分別是:
- .frm(儲存表定義);
- .MYD(MYData,儲存資料); l .MYI (MYIndex,儲存索引)。資料檔案和索引檔案可以放置在不同的目錄,平均分佈 IO,獲得更快的速度。要指定索引檔案和資料檔案的路徑,需要在建立表的時候通過 DATA DIRECTORY 和 INDEX DIRECTORY 語句指定,也就是說不同 MyISAM 表的索引檔案和資料檔案可以放置到不同的路徑下。檔案路徑需要是絕對路徑,並且具有訪問許可權。
MyISAM 型別的表可能會損壞,原因可能是多種多樣的,損壞後的表可能不能訪問,會提示需要修復或者訪問後返回錯誤的結果。MyISAM 型別的表提供修復的工具,可以用 CHECK TABLE 語句來檢查 MyISAM 表的健康,並用 REPAIR TABLE 語句修復一個損壞的 MyISAM 表。表損壞可能導致資料庫異常重新啟動,需要儘快修復並儘可能地確認損壞的原因。具體的操作步驟可以參見第 35 章應急處理。
MyISAM 的表又支援 3 種不同的儲存格式,分別是:
- 靜態(固定長度)表;
- 動態表; l 壓縮表。
其中,靜態表是預設的儲存格式。靜態表中的欄位都是非變長欄位,這樣每個記錄都是固定長度的,這種儲存方式的優點是儲存非常迅速,容易快取,出現故障容易恢復;缺點是佔用的空間通常比動態表多。靜態表的資料在儲存的時候會按照列的寬度定義補足空格,但是在應用訪問的時候並不會得到這些空格,這些空格在返回給應用之前已經去掉。
但是也有些需要特別注意的問題,如果需要儲存的內容後面本來就帶有空格,那麼在返回結果的時候也會被去掉,開發人員在編寫程式的時候需要特別注意,因為靜態表是預設的儲存格式,開發人員可能並沒有意識到這一點,從而丟失了尾部的空格。以下例子演示了插入的記錄包含空格時處理的情況:
mysql> create table Myisam_char (name char(10)) engine=myisam; Query OK, 0 rows affected (0.04 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) |
從上面的例子可以看出,插入記錄後面的空格都被去掉了,前面的空格保留。
動態表中包含變長欄位,記錄不是固定長度的,這樣儲存的優點是佔用的空間相對較少,但是頻繁地更新刪除記錄會產生碎片,需要定期執行 OPTIMIZE TABLE 語句或 myisamchk -r 命令來改善效能,並且出現故障的時候恢復相對比較困難。
壓縮表由 myisampack 工具建立,佔據非常小的磁碟空間。因為每個記錄是被單獨壓縮的,所以只有非常小的訪問開支。
7.2.2 InnoDB
InnoDB 儲存引擎提供了具有提交、回滾和崩潰恢復能力的事務安全。但是對比 MyISAM 的儲存引擎,InnoDB 寫的處理效率差一些並且會佔用更多的磁碟空間以保留資料和索引。
下面將重點介紹 InnoDB 儲存引擎的表使用過程中不同於其他儲存引擎的特點。
1、自動增長列
InnoDB 表的自動增長列可以手工插入,但是插入的值如果是空或者 0,則實際插入的將是自動增長後的值。下面定義新表 autoincre_demo,其中列 i 使用自動增長列,對該表插入記錄,然後檢視自動增長列的處理情況,可以發現插入 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.13 sec)
mysql> insert into autoincre_demo values(1,'1'),(0,'2'),(null,'3'); Query OK, 3 rows affected (0.04 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) |
可以通過“ALTER TABLE *** AUTO_INCREMENT = n;”語句強制設定自動增長列的初識值,預設從 1 開始,但是該強制的預設值是保留在記憶體中的,如果該值在使用之前資料庫重新啟動,那麼這個強制的預設值就會丟失,就需要在資料庫啟動以後重新設定。
可以使用 LAST_INSERT_ID()查詢當前執行緒最後插入記錄使用的值。如果一次插入了多條記錄,那麼返回的是第一條記錄使用的自動增長值。下面的例子演示了使用 LAST_INSERT_ID() 的情況:
mysql> insert into autoincre_demo values(4,'4');
Query OK, 1 row affected (0.04 sec)
mysql> select LAST_INSERT_ID(); +------------------+ | LAST_INSERT_ID() | +------------------+ | 2 | +------------------+ 1 row in set (0.00 sec)
mysql> insert into autoincre_demo(name) values('5'),('6'),('7'); Query OK, 3 rows affected (0.05 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> select LAST_INSERT_ID(); +------------------+ | LAST_INSERT_ID() | +------------------+ | 5 | +------------------+ 1 row in set (0.00 sec) |
對於 InnoDB 表,自動增長列必須是索引。如果是組合索引,也必須是組合索引的第一列,但是對於 MyISAM 表,自動增長列可以是組合索引的其他列,這樣插入記錄後,自動增長列是按照組合索引的前面幾列進行排序後遞增的。例如,建立一個新的 MyISAM 型別的表 autoincre_demo,自動增長列 d1 作為組合索引的第二列,對該表插入一些記錄後,可以發現自動增長列是按照組合索引的第一列 d2 進行排序後遞增的:
mysql> create table autoincre_demo -> (d1 smallint not null auto_increment, -> d2 smallint not null, -> name varchar(10), -> index(d2,d1) -> )engine=myisam; Query OK, 0 rows affected (0.03 sec)
mysql> insert into autoincre_demo(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_demo; +----+----+------+ | 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 表是父表,country_id 為主鍵索引,city 表是子表,country_id 欄位對 country 表的 country_id 有外來鍵。
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=utf8;
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), CONSTRAINT `fk_city_country` FOREIGN KEY (country_id) REFERENCES country (country_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; |
在建立索引的時候,可以指定在刪除、更新父表時,對子表進行的相應操作,包 RESTRICT、
CASCADE、SET NULL 和 NO ACTION。其中 RESTRICT 和 NO ACTION 相同,是指限制在子表有關聯記錄的情況下父表不能更新;CASCADE 表示父表在更新或者刪除時,更新或者刪除子表對應記錄;SET NULL 則表示父表在更新或者刪除的時候,子表的對應欄位被 SET NULL。選擇後兩種方式的時候要謹慎,可能會因為錯誤的操作導致資料的丟失。
例如對上面建立的兩個表,子表的外來鍵指定是 ON DELETE RESTRICT ON UPDATE CASCADE 方式的,那麼在主表刪除記錄的時候,如果子表有對應記錄,則不允許刪除,主表在更新記錄的時候,如果子表有對應記錄,則子表對應更新:
mysql> select * from country where country_id = 1; +------------+-------------+---------------------+
| country_id | country | last_update |
+------------+-------------+---------------------+
| 1 | Afghanistan | 2006-02-15 04:44:00 |
+------------+-------------+---------------------+
1 row in set (0.00 sec)
mysql> select * from city where country_id = 1; +---------+-------+------------+---------------------+ | city_id | city | country_id | last_update | +---------+-------+------------+---------------------+ | 251 | Kabul | 1 | 2006-02-15 04:45:25 | +---------+-------+------------+---------------------+ 1 row in set (0.00 sec)
mysql> delete from country where country_id=1; ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`sakila/city`, CONSTRAINT `fk_city_country` FOREIGN KEY (`country_id`) REFERENCES `country` (`country_id`) ON UPDATE CASCADE)
mysql> update country set country_id = 10000 where country_id = 1; Query OK, 1 row affected (0.04 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from country where country = 'Afghanistan'; +------------+-------------+---------------------+ | country_id | country | last_update | +------------+-------------+---------------------+ | 10000 | Afghanistan | 2007-07-17 09:45:23 | +------------+-------------+---------------------+ 1 row in set (0.00 sec) mysql> select * from city where city_id = 251; +---------+-------+------------+---------------------+ | city_id | city | country_id | last_update | +---------+-------+------------+---------------------+ | 251 | Kabul | 10000 | 2006-02-15 04:45:25 | +---------+-------+------------+---------------------+ 1 row in set (0.00 sec) |
當某個表被其他表建立了外來鍵參照,那麼該表的對應索引或者主鍵禁止被刪除。
在匯入多個表的資料時,如果需要忽略表之前的匯入順序,可以暫時關閉外來鍵的檢查;同樣,在執行 LOAD DATA 和 ALTER TABLE 操作的時候,可以通過暫時關閉外來鍵約束來加快處理的速度,關閉的命令是“SET FOREIGN_KEY_CHECKS = 0;”,執行完成之後,通過執行“SET FOREIGN_KEY_CHECKS = 1;”語句改回原狀態。
對於 InnoDB 型別的表,外來鍵的資訊通過使用 show create table 或者 show table status 命令都可以顯示。
mysql> show table status like 'city' \G
*************************** 1. row ***************************
Name: city
Engine: InnoDB Version: 10 Row_format: Compact Rows: 427 Avg_row_length: 115 Data_length: 49152 Max_data_length: 0 Index_length: 16384 Data_free: 0 Auto_increment: 601 Create_time: 2007-07-17 09:45:33 Update_time: NULL Check_time: NULL Collation: utf8_general_ci Checksum: NULL Create_options: Comment: InnoDB free: 0 kB; (`country_id`) REFER `sakila/country`(`country_id`) ON UPDATE 1 row in set (0.00 sec) |
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 427
Avg_row_length: 115
Data_length: 49152 Max_data_length: 0
Index_length: 16384
Data_free: 0
Auto_increment: 601
Create_time: 2007-07-17 09:45:33
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL Create_options:
Comment: InnoDB free: 0 kB; (`country_id`) REFER `sakila/country`(`country_id`) ON
UPDATE
1 row in set (0.00 sec)
3、儲存方式
InnoDB 儲存表和索引有以下兩種方式。
- 使用共享表空間儲存,這種方式建立的表的表結構儲存在.frm 檔案中,資料和索引儲存在 innodb_data_home_dir 和 innodb_data_file_path 定義的表空間中,可以是多個檔案。
- 使用多表空間儲存,這種方式建立的表的表結構仍然儲存在.frm 檔案中,但是每個表的資料和索引單獨儲存在.ibd 中。如果是個分割槽表,則每個分割槽對應單獨的.ibd 檔案,檔名是“表名+分割槽名”,可以在建立分割槽的時候指定每個分割槽的資料檔案的位置,以此來將表的 IO 均勻分佈在多個磁碟上。
要使用多表空間的儲存方式,需要設定引數 innodb_file_per_table,並重新啟動服務後才可以生效,對於新建的表按照多表空間的方式建立,已有的表仍然使用共享表空間儲存。如果將已有的多表空間方式修改回共享表空間的方式,則新建表會在共享表空間中建立,但已有的多表空間的表仍然儲存原來的訪問方式。所以多表空間的引數生效後,只對新建的表生效。
多表空間的資料檔案沒有大小限制,不需要設定初始大小,也不需要設定檔案的最大限制、擴充套件大小等引數。
對於使用多表空間特性的表,可以比較方便地進行單表備份和恢復操作,但是直接複製.ibd 檔案是不行的,因為沒有共享表空間的資料字典資訊,直接複製的.ibd 檔案和.frm 檔案恢復時是不能被正確識別的,但可以通過以下命令:
ALTER TABLE tbl_name DISCARD TABLESPACE;
ALTER TABLE tbl_name IMPORT TABLESPACE;
將備份恢復到資料庫中,但是這樣的單表備份,只能恢復到表原來在的資料庫中,而不能恢復到其他的資料庫中。如果要將單表恢復到目標資料庫,則需要通過 mysqldump 和 mysqlimport 來實現。
注意:即便在多表空間的儲存方式下,共享表空間仍然是必須的,InnoDB 把內部資料詞典和未作日誌放在這個檔案中。