原創文章,轉載必需註明出處:http://www.cnblogs.com/wu-jian/php
前言mysql
需求源自項目中的MemCache需求,開始想用MemCached(官方網站:http://memcached.org/ ),但這個在Linux如下應用普遍的開源軟件無官方支持的Windows版本號。後來看到博客園在用NorthScale Memcached Server(官方網站:http://www.couchbase.com/products-and-services/memcached)。貌似共享收費,又猶豫了。sql
事實上項目裏的需求很是easy,也想本身用.Net Cache來實現,但穩定性難以評估,開發維護成本又彷佛太大,沒辦法。My SQL Memory Storage成了惟一選擇,因爲差點兒不怎麼需要編寫代碼。數據庫
先看官方手冊,而後寫了個簡單的性能測試。因爲官方最新的文檔都是英文版的,因此譯了5.5版本號 MySQL Memory Storage章節。緩存
官方文檔(譯自5.5版本號的The Memory Storage Engine)多線程
Memory存儲引擎將表的數據存放在內存中。Memory替代曾經的Heap成爲首選項。但同一時候向下兼容。Heap仍被支持。架構
Memory存儲引擎特性:併發
Storage limits | RAM | Transactions | No | Locking granularity | Table |
MVCC | No | Geospatial data type support | No | Geospatial indexing support | No |
B-tree indexes | Yes | Hash indexes | Yes | Full-text search indexes | No |
Clustered indexes | No | Data caches | N/A | Index caches | N/A |
Compressed data | No | Encrypted data | Yes | Cluster database support | No |
Replication support | Yes | Foreign key support | No | Backup / point-in-time recoveryc | Yes |
Query cache support | Yes | Update statistics for data dictionary | Yes |
Memory 與 MySQL Cluster的比較分佈式
但願部署內存引擎的開發人員們會考慮MySQL Cluster是不是更好的選擇,參考例如如下Memory引擎的使用場景及特色:memcached
但是內存表的性能受制於單線程的運行效率和寫操做時的表鎖開銷。這就限制了內存表高負載時的擴展性,特別是混合寫操做的併發處理。此外,內存表中的數據在server從新啓動後會丟失。
MySQL Cluster(集羣)支持與Memory引擎相同的功能並且提供更高的性能,同一時候擁有Memory不支持的不少其餘其餘功能:
關於MySQL集羣與Memory引擎不少其它細節方面的比較,可以查看Scaling Web Services with MySQL Cluster: An Alternative to the MySQL Memory Storage Engine。該白皮書包含了這兩種技術的性能研究。並一步步指導你怎樣將Memory用戶遷移到MySQL集羣。
每個Memory表和一個磁盤文件關聯起來。文件名稱由表的名字開始。並且由一個.frm的擴展名來指明它存儲的表定義。
要明白指出你想要一個Memory表。可以使用ENGINE選項來指定:
CREATE TABLE t (i INT) ENGINE = MEMORY;
如它們名字所指明的,Memory表被存儲在內存中。且默認使用哈希索引。這使得它們很快,並且對建立暫時表很實用。可是。當server關閉之時,所有存儲在Memory表裏的數據被丟失。
因爲表的定義被存在磁盤上的.frm文件裏,因此表自身繼續存在。在server從新啓動動時它們是空的。
這個樣例顯示你怎樣可以建立。使用並刪除一個Memory表:
CREATE TABLE test ENGINE=MEMORY; SELECT ip,SUM(downloads) AS down FROM log_table GROUP BY ip; SELECT COUNT(ip),AVG(down) FROM test; DROP TABLE test;
MEMORY表有下列特徵:
CREATE TABLE lookup (id INT, INDEX USING HASH (id)) ENGINE = MEMORY; CREATE TABLE lookup (id INT, INDEX USING BTREE (id)) ENGINE = MEMORY;
(對哈希索引的實現,這是一個不常用的功能)
二、MEMORY表最大值受系統變量 max_heap_table_size 限制,默以爲16MB,要改變MEMORY表限制大小,需要改變max_heap_table_size 的值。該值在 CREATE TABLE 時生效並伴隨表的生命週期,(當你使用 ALTER TABLE 或 TRUNCATE TABLE命令時,表的最大限制將改變。或從新啓動MYSQL服務時, 所有已存在的MEMORY表的最大限制將使用max_heap_table_size 的值重置。)
自從server啓動後。當一個MEMORY表在主server上第一次被使用之時,一個DELETE FROM語句被本身主動寫進主server的二進制日誌,所以再次讓從server與主server同步。
注意,即便使用這個策略,在主server的從新啓動和它第一次使用該表之間的間隔中,從server仍舊在表中有過期數據。可是,假設你使用--init-file選項於主server啓動之時在其上推行MEMORY表。它確保這個時間間隔爲零。
SUM_OVER_ALL_BTREE_KEYS(max_length_of_key + sizeof(char*) * 4) + SUM_OVER_ALL_HASH_KEYS(sizeof(char*) * 2) + ALIGN(length_of_row+1, sizeof(char*))
sizeof(char*)在32位機器上是4。在64位機器上是8。
如前所述。系統變量 max_heap_table_size 用於設置內存表的大小上限。要控制單個表的最大值。需要在建立表以前設置會話變量。(不要設置全局max_heap_table_size 的值。除非你打算所有client建立的內存表都使用這個值)
如下的樣例建立了兩張內存表,它們的限制大小分別爲 1MB 和 2MB:
SET max_heap_table_size = 1024*1024; /* Query OK, 0 rows affected (0.00 sec) */ CREATE TABLE t1 (id INT, UNIQUE(id)) ENGINE = MEMORY; /* Query OK, 0 rows affected (0.01 sec) */ SET max_heap_table_size = 1024*1024*2; /* Query OK, 0 rows affected (0.00 sec) */ CREATE TABLE t2 (id INT, UNIQUE(id)) ENGINE = MEMORY; /* Query OK, 0 rows affected (0.00 sec) */
Memory存儲引擎官方論壇: http://forums.mysql.com/list.php?
性能測試
分別測試比較了MySQL的InnoDB、MyIsam、Memory三種引擎與.Net DataTable的Insert以及Select性能(柱狀圖體現了其消耗時間,單位百納秒。innodb_flush_log_at_trx_commit參數配置爲1,每次測試從新啓動了MySQL以免Query Cache)。大至結果例如如下:
寫入10000條記錄比較。
讀取1000條記錄比較。
測試腳本:
/****************************************************** MYSQL STORAGE ENGINE TEST http://wu-jian.cnblogs.com/ 2011-11-29 ******************************************************/ CREATE DATABASE IF NOT EXISTS test CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'; USE test; /****************************************************** 1.INNODB ******************************************************/ DROP TABLE IF EXISTS test_innodb; CREATE TABLE IF NOT EXISTS test_innodb ( id INT UNSIGNED AUTO_INCREMENT COMMENT 'PK', obj CHAR(255) NOT NULL DEFAULT '' COMMENT 'OBJECT', PRIMARY KEY (id) ) ENGINE=INNODB; /****************************************************** 2.MYISAM ******************************************************/ DROP TABLE IF EXISTS test_myisam; CREATE TABLE IF NOT EXISTS test_myisam ( id INT UNSIGNED AUTO_INCREMENT COMMENT 'PK', obj CHAR(255) NOT NULL DEFAULT '' COMMENT 'OBJECT', PRIMARY KEY (id) ) ENGINE=MYISAM; /****************************************************** 1.MEMORY ******************************************************/ DROP TABLE IF EXISTS test_memory; CREATE TABLE IF NOT EXISTS test_memory ( id INT UNSIGNED AUTO_INCREMENT COMMENT 'PK', obj CHAR(255) NOT NULL DEFAULT '' COMMENT 'OBJECT', PRIMARY KEY (id) ) ENGINE=MEMORY;
測試代碼:
using System; using System.Data; using MySql.Data.MySqlClient; namespace MySqlEngineTest { class Program { const string OBJ = "The MEMORY storage engine creates tables with contents that are stored in memory. Formerly, these were known as HEAP tables. MEMORY is the preferred term, although HEAP remains supported for backward compatibility."; const string SQL_CONN = "Data Source=127.0.0.1;Port=3308;User ID=root;Password=root;DataBase=test;Allow Zero Datetime=true;Charset=utf8;pooling=true;"; const int LOOP_TOTAL = 10000; const int LOOP_BEGIN = 8000; const int LOOP_END = 9000; #region Database Functions public static bool DB_InnoDBInsert(string obj) { string commandText = "INSERT INTO test_innodb (obj) VALUES (?obj)"; MySqlParameter[] parameters = { new MySqlParameter("?
obj", MySqlDbType.VarChar, 255) }; parameters[0].Value = obj; if (DBUtility.MySqlHelper.ExecuteNonQuery(SQL_CONN, CommandType.Text, commandText, parameters) > 0) return true; else return false; } public static string DB_InnoDBSelect(int id) { string commandText = "SELECT obj FROM test_innodb WHERE id = ?id"; MySqlParameter[] parameters = { new MySqlParameter("?id", MySqlDbType.Int32) }; parameters[0].Value = id; return DBUtility.MySqlHelper.ExecuteScalar(SQL_CONN, CommandType.Text, commandText, parameters).ToString(); } public static bool DB_MyIsamInsert(string obj) { string commandText = "INSERT INTO test_myisam (obj) VALUES (?obj)"; MySqlParameter[] parameters = { new MySqlParameter("?obj", MySqlDbType.VarChar, 255) }; parameters[0].Value = obj; if (DBUtility.MySqlHelper.ExecuteNonQuery(SQL_CONN, CommandType.Text, commandText, parameters) > 0) return true; else return false; } public static string DB_MyIsamSelect(int id) { string commandText = "SELECT obj FROM test_myisam WHERE id = ?id"; MySqlParameter[] parameters = { new MySqlParameter("?id", MySqlDbType.Int32) }; parameters[0].Value = id; return DBUtility.MySqlHelper.ExecuteScalar(SQL_CONN, CommandType.Text, commandText, parameters).ToString(); } public static bool DB_MemoryInsert(string obj) { string commandText = "INSERT INTO test_memory (obj) VALUES (?obj)"; MySqlParameter[] parameters = { new MySqlParameter("?
obj", MySqlDbType.VarChar, 255) }; parameters[0].Value = obj; if (DBUtility.MySqlHelper.ExecuteNonQuery(SQL_CONN, CommandType.Text, commandText, parameters) > 0) return true; else return false; } public static string DB_MemorySelect(int id) { string commandText = "SELECT obj FROM test_memory WHERE id = ?id"; MySqlParameter[] parameters = { new MySqlParameter("?id", MySqlDbType.Int32) }; parameters[0].Value = id; return DBUtility.MySqlHelper.ExecuteScalar(SQL_CONN, CommandType.Text, commandText, parameters).ToString(); } #endregion #region Test Functions InnoDB static void InnoDBInsert() { long begin = DateTime.Now.Ticks; for (int i = 0; i < LOOP_TOTAL; i++) { DB_InnoDBInsert(OBJ); } Console.WriteLine("InnoDB Insert Result: {0}", DateTime.Now.Ticks - begin); } static void InnoDBSelect() { long begin = DateTime.Now.Ticks; for (int i = LOOP_BEGIN; i < LOOP_END; i++) { DB_InnoDBSelect(i); } Console.WriteLine("InnoDB SELECT Result: {0}", DateTime.Now.Ticks - begin); } static void MyIsamInsert() { long begin = DateTime.Now.Ticks; for (int i = 0; i < LOOP_TOTAL; i++) { DB_MyIsamInsert(OBJ); } Console.WriteLine("MyIsam Insert Result: {0}", DateTime.Now.Ticks - begin); } static void MyIsamSelect() { long begin = DateTime.Now.Ticks; for (int i = LOOP_BEGIN; i < LOOP_END; i++) { DB_MyIsamSelect(i); } Console.WriteLine("MyIsam SELECT Result: {0}", DateTime.Now.Ticks - begin); } static void MemoryInsert() { long begin = DateTime.Now.Ticks; for (int i = 0; i < LOOP_TOTAL; i++) { DB_MemoryInsert(OBJ); } Console.WriteLine("Memory Insert Result: {0}", DateTime.Now.Ticks - begin); } static void MemorySelect() { long begin = DateTime.Now.Ticks; for (int i = LOOP_BEGIN; i < LOOP_END; i++) { DB_MemorySelect(i); } Console.WriteLine("Memory SELECT Result: {0}", DateTime.Now.Ticks - begin); } static void DataTableInsertAndSelect() { //Insert DataTable dt = new DataTable(); dt.Columns.Add("id", Type.GetType("System.Int32")); dt.Columns["id"].AutoIncrement = true; dt.Columns.Add("obj", Type.GetType("System.String")); DataRow dr = null; long begin = DateTime.Now.Ticks; for (int i = 0; i < LOOP_TOTAL; i++) { dr = null; dr = dt.NewRow(); dr["obj"] = OBJ; dt.Rows.Add(dr); } Console.WriteLine("DataTable Insert Result: {0}", DateTime.Now.Ticks - begin); //Select long begin1 = DateTime.Now.Ticks; for (int i = LOOP_BEGIN; i < LOOP_END; i++) { dt.Select("id = " + i); } Console.WriteLine("DataTable Select Result: {0}", DateTime.Now.Ticks - begin1); } #endregion static void Main(string[] args) { InnoDBInsert(); InnoDBSelect(); //restart mysql to avoid query cache MyIsamInsert(); MyIsamSelect(); //restart mysql to avoid query cache MemoryInsert(); MemorySelect(); DataTableInsertAndSelect(); } }//end class }
總結
.Net Cache讀寫性能毫無疑問大大率先於數據庫引擎
InnoDB寫入耗時大概是MyIsam和Memory的5倍左右。它的行鎖機制一定決定了寫入時的不少其它性能開銷,而它的強項在於多線程的併發處理,而本測試未能體現其優點。
三種數據庫引擎在SELECT性能上差點兒相同,Memory稍佔優,相同高併發下的比較有待進一步測試。
<全文完>