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語句做一定的“分解”,使每一步查詢都能在較短時間完成,從而減少鎖衝突。如果複雜查詢不可避免,應儘量安排在資料庫空閒時段執行,比如一些定期統計可以安排在夜間執行。