mysql儲存引擎MyISAM

1,建立myisam表

mysql> create table t (id int , name varchar(30) , msg varchar(100)) engine = MyISAM;
mysql> show table status like "t" G ;
*************************** 1. row ***************************
Name: t
Engine: MyISAM
Version: 10
Row_format: Dynamic
Rows: 0
Avg_row_length: 0
Data_length: 0
Max_data_length: 281474976710655
Index_length: 1024
Data_free: 0
Auto_increment: NULL
Create_time: 2013-09-12 00:39:29
Update_time: 2013-09-12 00:39:29
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)

2,auto_increment

當使用這個引數的時候,這個列一定要是主鍵

mysql> create table tt (id int auto_increment primary key  , name varchar(30) , msg varchar(100)) engine = MyISAM;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into tt(name,msg) values(`chenzhongyang`,`good`);
Query OK, 1 row affected (0.00 sec)
雖然我們沒有指定名字是chenzhongyang的id是1,但是有了auto_increment這個引數,系統會自動給他加上1
mysql> select * from tt;
+----+---------------+------+
| id | name          | msg  |
+----+---------------+------+
|  1 | chenzhongyang | good |
+----+---------------+------+
1 row in set (0.01 sec)

我們還可以設定auto_increment的值,但是這個值設定了的話,就會從這個值開始累積

mysql> alter table tt auto_increment=2000;
Query OK, 1 row affected (0.02 sec)
Records: 1  Duplicates: 0  Warnings: 0
mysql> insert into tt(name,msg) values(`tianhongyan`,`baby`);
Query OK, 1 row affected (0.00 sec)
mysql> select * from tt;
+------+---------------+------+
| id   | name          | msg  |
+------+---------------+------+
|    1 | chenzhongyang | good |
| 2000 | tianhongyan   | baby |
+------+---------------+------+
2 rows in set (0.00 sec)
mysql> insert into tt(name,msg) values(`zhongguo`,`XXXXXXX-YYYYYYYYY-+VVVV`);
Query OK, 1 row affected (0.00 sec)
mysql> select * FROM tt;
+------+---------------+-------------------------+
| id   | name          | msg                     |
+------+---------------+-------------------------+
|    1 | chenzhongyang | good                    |
| 2000 | tianhongyan   | baby                    |
| 2001 | zhongguo      | XXXXXXX-YYYYYYYYY-+VVVV |
+------+---------------+-------------------------+
3 rows in set (0.00 sec)

還有一個函式比較有用last_insert_id()。這個函式可以查出最後一次insert的id

mysql> select  last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|             2001 |
+------------------+
1 row in set (0.00 sec)

3,儲存結構

資料檔案(.MYD),索引檔案(.MYI)和結構檔案(.frm)

特點:可以在不同伺服器上拷貝資料檔案和索引檔案。

如果我們把索引檔案和資料檔案放到不同的機器上,那麼可以提高系統i/o

4,不支援事務

即使我們關閉autocommit,myisam引擎還是會立即執行我們的命令,這個時候rollback已經沒有用了

mysql> show variables like "%autocommit%";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)
mysql> set autocommit=OFF ;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like "%autocommit%";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | OFF   |
+---------------+-------+
1 row in set (0.00 sec)
mysql> delete from tt where id=1;
Query OK, 1 row affected (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> select   * from tt;
+------+-------------+-------------------------+
| id   | name        | msg                     |
+------+-------------+-------------------------+
| 2000 | tianhongyan | baby                    |
| 2001 | zhongguo    | XXXXXXX-YYYYYYYYY-+VVVV |
+------+-------------+-------------------------+
2 rows in set (0.00 sec)

5,myisam_data_pointer_size

預設的指標大小是6byte,一個位元組是8bit那麼資料檔案的大小就是2的6*8次方byte

也就是1024*1024*1024*1024*256/1024/1024/1024/1024=256TB

mysql> show variables like "%pointer%";
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| myisam_data_pointer_size | 6     |
+--------------------------+-------+
1 row in set (0.00 sec)

我們來做個實驗試試


如果myisam_data_pointer_size=2,那麼就意味著一個表的最大資料檔案是65535/1024=64K
mysql> set global myisam_data_pointer_size=2;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like "%pointer%";
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| myisam_data_pointer_size | 2     |
+--------------------------+-------+
1 row in set (0.00 sec)
我們來建立一個 大表ss
mysql> create table ss select * from information_schema.tables ;
Query OK, 54 rows affected (0.09 sec)
Records: 54  Duplicates: 0  Warnings: 0
mysql> insert into ss select * from ss;
Query OK, 108 rows affected (0.01 sec)
Records: 108  Duplicates: 0  Warnings: 0
mysql> insert into ss select * from ss;
Query OK, 216 rows affected (0.01 sec)
Records: 216  Duplicates: 0  Warnings: 0
這個時候出現了表ss滿了的錯誤,我們看看資料檔案 是64K,要想繼續可以插入資料,那麼就要把這個引數調大
mysql> insert into ss select * from ss;
ERROR 1114 (HY000): The table `ss` is full
mysql> insert into ss select * from ss;
ERROR 1114 (HY000): The table `ss` is full
mysql> insert into ss select * from ss;
ERROR 1114 (HY000): The table `ss` is full
[root@test3 test]# ls -lh
total 116K
-rw-rw----. 1 mysql mysql 9.3K Sep 12 06:44 ss.frm
-rw-rw----. 1 mysql mysql  64K Sep 12 06:44 ss.MYD
-rw-rw----. 1 mysql mysql 1.0K Sep 12 06:44 ss.MYI
mysql> insert into ss select * from ss;
ERROR 1114 (HY000): The table `ss` is full
mysql> alter table ss max_ROWS=10000000000 ;
Query OK, 496 rows affected (0.11 sec)
Records: 496  Duplicates: 0  Warnings: 0
mysql> insert into ss select * from ss;
Query OK, 496 rows affected (0.02 sec)
Records: 496  Duplicates: 0  Warnings: 0

6,myisam的儲存行格式

MyISAM支援三種不同儲存格式。

其中兩個(固定格式和動態格式)根據正使用的列的型別來自動選擇。第三個,即已壓縮格式,只能使用myisampack工具來建立。

1.fixed靜態格式(固定長度)表的一般特徵:

·CHAR列對列寬度是空間填補的。

·非常快。

·容易快取。

·崩潰後容易重建,因為記錄位於固定位置。

·重新組織是不必要的,除非你刪除巨量的記錄並且希望為作業系統騰出磁碟空間。為此,可使用OPTIMIZETABLE或者myisamchk-r

·通常比動態格式表需要更多的磁碟空間。

2.dynamic動態格式表的一般特徵:

·除了長度少於4的列外,所有的字串列是動態的。

·在每個記錄前面是一個點陣圖,該點陣圖表明哪一列包含空字串(對於字串列)或者0(對於數字列)。注意,這並不包括包含NULL值的列。如果一個字元列在拖曳空間移除後長度為零,或者一個數字列為零值,這都在點陣圖中標註了且列不被儲存到磁碟。非空字串被存為一個長度位元組加字元串的內容。

·通常比固定長度表需要更少的磁碟空間。

·每個記錄僅使用必需大小的空間。儘管如此,如果一個記錄變大,它就按需要被分開成多片,造成記錄碎片的後果。比如,你用擴充套件行長度的資訊更新一行,該行就變得有碎片。在這種情況下,你可以時不時執行OPTIMIZETABLE或myisamchk-r來改善效能。可使用myisamchk-ei來獲取表的統計資料。

·動態格式表在崩潰後要比靜態格式表更難重建,因為一個記錄可能被分為多個碎片且連結(碎片)可能被丟失。

3.已壓縮表有下列特徵:

·已壓縮表佔據非常小的磁碟空間。這最小化了磁碟用量,當使用緩慢的磁碟(如CD-ROM)之時,這是很有用的。

·每個記錄是被單獨壓縮的,所以只有非常小的訪問開支。依據表中最大的記錄,一個記錄的頭在每個表中佔據1到3個位元組。每個列被不同地壓縮。通常每個列有一個不同的Huffman樹。一些壓縮型別如下:

字尾空間壓縮。

字首空間壓縮。

零值的數用一個位來儲存。

如果在一個整型列中的值有一個小的範圍,列被用最小可能的型別來儲存。比如,一個BIGINT列(8位元組),如果所有它的值在-128到127範圍內,它可以被儲存為TINYINT列(1位元組)

如果一個列僅有一小組可能的值,列的型別被轉化成ENUM。

一個列可以使用先前壓縮型別的任意合併。

·可以處理固定長度或動態長度記錄。

7,加鎖和併發

MyISAM儲存引擎只支援表鎖,這也是MySQL開始幾個版本中唯一支援的鎖型別。隨著應用對事務完整性和併發性要求的不斷提高,MySQL才開始開發基於事務的儲存引擎,後來慢慢出現了支援頁鎖的BDB儲存引擎和支援行鎖的InnoDB儲存引擎(實際InnoDB是單獨的一個公司,現在已經被Oracle公司收購)。但是MyISAM的表鎖依然是使用最為廣泛的鎖型別

加鎖:對整張表進行加鎖,而不是行。
併發:在讀資料的時候,所有的表上都可以獲得共享鎖(讀鎖),每個連線都不互相干擾。
在寫資料的時候,獲得排他鎖,會把整個表進行加鎖,而其他的連線請求(讀,寫請求)都處於等待中。

可以通過檢查table_locks_waited和table_locks_immediate狀態變數來分析系統上的表鎖定爭奪:

mysql> show status like `table%`;
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 2979 |
| Table_locks_waited | 0 |
+-----------------------+-------+
2 rows in set (0.00 sec))

如果Table_locks_waited的值比較高,則說明存在著較嚴重的表級鎖爭用情況。

對MyISAM表的讀操作,不會阻塞其他使用者對同一表的讀請求,但會阻塞對同一表的寫請求;對MyISAM表的寫操作,則會阻塞其他使用者對同一表的讀和寫操作;MyISAM表的讀操作與寫操作之間,以及寫操作之間是序列的

MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行更新操作(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖,這個過程並不需要使用者干預,因此,使用者一般不需要直接用LOCKTABLE命令給MyISAM表顯式加鎖。顯式加鎖基本上都是為了方便而已,並非必須如此。也正是因為這樣,所以myisam不會產生死鎖。

READ鎖表

獲得表film_text的READ鎖定
mysql> lock table film_text read;
Query OK, 0 rows affected (0.00 sec)
當前session可以查詢該表記錄
mysql> select film_id,title from film_text where film_id = 1001;
+---------+------------------+
| film_id | title            |
+---------+------------------+
| 1001     | ACADEMY DINOSAUR |
+---------+------------------+
1 row in set (0.00 sec)
其他session也可以查詢該表的記錄
mysql> select film_id,title from film_text where film_id = 1001;
+---------+------------------+
| film_id | title            |
+---------+------------------+
| 1001    | ACADEMY DINOSAUR |
+---------+------------------+
1 row in set (0.00 sec)
當前session不能查詢沒有鎖定的表
mysql> select film_id,title from film where film_id = 1001;
ERROR 1100 (HY000): Table `film` was not locked with LOCK TABLES
其他session可以查詢或者更新未鎖定的表
mysql> select film_id,title from film where film_id = 1001;
+---------+---------------+
| film_id | title         |
+---------+---------------+
| 1001    | update record |
+---------+---------------+
1 row in set (0.00 sec)
mysql> update film set title = `Test` where film_id = 1001;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1 Changed: 1 Warnings: 0
當前session中插入或者更新鎖定的表都會提示錯誤:
mysql> insert into film_text (film_id,title) values(1002,`Test`);
ERROR 1099 (HY000): Table `film_text` was locked with a READ lock and can`t be updated
mysql> update film_text set title = `Test` where film_id = 1001;
ERROR 1099 (HY000): Table `film_text` was locked with a READ lock and can`t be updated
其他session更新鎖定表會等待獲得鎖:
mysql> update film_text set title = `Test` where film_id = 1001;
等待
釋放鎖
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
等待
Session獲得鎖,更新操作完成:
mysql> update film_text set title = `Test` where film_id = 1001;
Query OK, 1 row affected (1 min 0.71 sec)
Rows matched: 1 Changed: 1 Warnings: 0
當使用LOCK TABLES時,不僅需要一次鎖定用到的所有表,而且,同一個表在SQL語句中出現多少次,就要通過與SQL語句中相同的別名鎖定多少次,否則也會出錯!舉例說明如下。
(1)對actor表獲得讀鎖:
mysql> lock table actor read;
Query OK, 0 rows affected (0.00 sec)
(2)但是通過別名訪問會提示錯誤:
mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = `Lisa` and a.last_name = `Tom` and a.last_name <> b.last_name;
ERROR 1100 (HY000): Table `a` was not locked with LOCK TABLES
(3)需要對別名分別鎖定:
mysql> lock table actor as a read,actor as b read;
Query OK, 0 rows affected (0.00 sec)
(4)按照別名的查詢可以正確執行:
mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = `Lisa` and a.last_name = `Tom` and a.last_name <> b.last_name;
+------------+-----------+------------+-----------+
| first_name | last_name | first_name | last_name |
+------------+-----------+------------+-----------+
| Lisa       | Tom       | LISA       | MONROE    |
+------------+-----------+------------+-----------+
1 row in set (0.00 sec)

WRITE鎖表

獲得表film_text的WRITE鎖定
mysql> lock table film_text write;
Query OK, 0 rows affected (0.00 sec)
當前session對鎖定表的查詢、更新、插入操作都可以執行:
mysql> select film_id,title from film_text where film_id = 1001;
+---------+-------------+
| film_id | title |
+---------+-------------+
| 1001 | Update Test |
+---------+-------------+
1 row in set (0.00 sec)
mysql> insert into film_text (film_id,title) values(1003,`Test`);
Query OK, 1 row affected (0.00 sec)
mysql> update film_text set title = `Test` where film_id = 1001;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
其他session對鎖定表的查詢被阻塞,需要等待鎖被釋放:
mysql> select film_id,title from film_text where film_id = 1001;
等待
釋放鎖:

併發插入(ConcurrentInserts)

上文提到過MyISAM表的讀和寫是序列的,但這是就總體而言的。在一定條件下,MyISAM表也支援查詢和插入操作的併發進行。

MyISAM儲存引擎有一個系統變數concurrent_insert,專門用以控制其併發插入的行為,其值分別可以為0、1或2。

l當concurrent_insert設定為0時,不允許併發插入。

l當concurrent_insert設定為1時,如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個程式讀表的同時,另一個程式從表尾插入記錄。這也是MySQL的預設設定。

l當concurrent_insert設定為2時,無論MyISAM表中有沒有空洞,都允許在表尾併發插入記錄。

在如表20-4所示的例子中,session_1獲得了一個表的READLOCAL鎖,該執行緒可以對錶進行查詢操作,但不能對錶進行更新操作;其他的執行緒(session_2),雖然不能對錶進行刪除和更新操作,但卻可以對該表進行併發插入操作,這裡假設該表中間不存在空洞。

表20-4MyISAM儲存引擎的讀寫(INSERT)併發例子

session_1
session_2
獲得表film_text的READ LOCAL鎖定
mysql> lock table film_text read local;
Query OK, 0 rows affected (0.00 sec)
當前session不能對鎖定表進行更新或者插入操作:
mysql> insert into film_text (film_id,title) values(1002,`Test`);
ERROR 1099 (HY000): Table `film_text` was locked with a READ lock and can`t be updated
mysql> update film_text set title = `Test` where film_id = 1001;
ERROR 1099 (HY000): Table `film_text` was locked with a READ lock and can`t be updated
其他session可以進行插入操作,但是更新會等待:
mysql> insert into film_text (film_id,title) values(1002,`Test`);
Query OK, 1 row affected (0.00 sec)
mysql> update film_text set title = `Update Test` where film_id = 1001;
等待
當前session不能訪問其他session插入的記錄:
mysql> select film_id,title from film_text where film_id = 1002;
Empty set (0.00 sec)
釋放鎖:
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
等待
當前session解鎖後可以獲得其他session插入的記錄:
mysql> select film_id,title from film_text where film_id = 1002;
+---------+-------+
| film_id | title |
+---------+-------+
| 1002 | Test |
+---------+-------+
1 row in set (0.00 sec)
Session2獲得鎖,更新操作完成:
mysql> update film_text set title = `Update Test` where film_id = 1001;
Query OK, 1 row affected (1 min 17.75 sec)
Rows matched: 1 Changed: 1 Warnings: 0

可以利用MyISAM儲存引擎的併發插入特性,來解決應用中對同一表查詢和插入的鎖爭用。例如,將concurrent_insert系統變數設為2,總是允許併發插入;同時,通過定期在系統空閒時段執行OPTIMIZETABLE語句來整理空間碎片,收回因刪除記錄而產生的中間空洞

MyISAM的鎖排程

MyISAM儲存引擎的讀鎖和寫鎖是互斥的,讀寫操作是序列的。那麼,一個程式請求某個MyISAM表的讀鎖,同時另一個程式也請求同一表的寫鎖,MySQL如何處理呢?答案是寫程式先獲得鎖。不僅如此,即使讀請求先到鎖等待佇列,寫請求後到,寫鎖也會插到讀鎖請求之前!這是因為MySQL認為寫請求一般比讀請求要重要。這也正是MyISAM表不太適合於有大量更新操作和查詢操作應用的原因,因為,大量的更新操作會造成查詢操作很難獲得讀鎖,從而可能永遠阻塞。這種情況有時可能會變得非常糟糕!幸好我們可以通過一些設定來調節MyISAM的排程行為。

通過指定啟動引數low-priority-updates,使MyISAM引擎預設給予讀請求以優先的權利。

通過執行命令SETLOW_PRIORITY_UPDATES=1,使該連線發出的更新請求優先順序降低。

通過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優先順序。

雖然上面3種方法都是要麼更新優先,要麼查詢優先的方法,但還是可以用其來解決查詢相對重要的應用(如使用者登入系統)中,讀鎖等待嚴重的問題。

另外,MySQL也提供了一種折中的辦法來調節讀寫衝突,即給系統引數max_write_lock_count設定一個合適的值,當一個表的讀鎖達到這個值後,MySQL就暫時將寫請求的優先順序降低,給讀程式一定獲得鎖的機會。

上面已經討論了寫優先排程機制帶來的問題和解決辦法。這裡還要強調一點:一些需要長時間執行的查詢操作,也會使寫程式“餓死”!因此,應用中應儘量避免出現長時間執行的查詢操作,不要總想用一條SELECT語句來解決問題,因為這種看似巧妙的SQL語句,往往比較複雜,執行時間較長,在可能的情況下可以通過使用中間表等措施對SQL語句做一定的“分解”,使每一步查詢都能在較短時間完成,從而減少鎖衝突。如果複雜查詢不可避免,應儘量安排在資料庫空閒時段執行,比如一些定期統計可以安排在夜間執行。