bitsCN.com
吴剑 2011-11-29
http://wu-jian.cnblogs.com/
需求源自项目中的MemCache需求,开始想用MemCached(官方站点:http://memcached.org/ ),但这个在Linux下面应用广泛的开源软件无官方支持的Windows版本。后来看到博客园在用NorthScale Memcached Server(官方站点:http://www.couchbase.com/products-and-services/memcached),貌似共享收费,又犹豫了。其实项目里的需求很简单,也想自己用.Net Cache来实现,但稳定性难以评估,开发维护成本又似乎太大,没办法,My SQL Memory Storage成了唯一选择,因为几乎不怎么需要编写代码。
先看官方手册,然后写了个简单的性能测试。因为官方最新的文档都是英文版的,所以译了5.5版本 MySQL Memory Storage章节。
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引擎的使用场景及特点:
但是内存表的性能受制于单线程的执行效率和写操作时的表锁开销,这就限制了内存表高负载时的扩展性,特别是混合写操作的并发处理。此外,内存表中的数据在服务器重启后会丢失。
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表被存储在内存中,且默认使用哈希索引。这使得它们非常快,并且对创建临时表非常有用。可是,当服务器关闭之时,所有存储在Memory表里的数据被丢失。因为表的定义被存在磁盘上的.frm文件中,所以表自身继续存在,在服务器重启动时它们是空的。
这个例子显示你如何可以创建,使用并删除一个Memory表:
CREATE TABLE test ENGINE=MEMORY;<br>SELECT ip,SUM(downloads) AS down FROM log_table GROUP BY ip;<br>SELECT COUNT(ip),AVG(down) FROM test;<br>DROP TABLE test;
MEMORY表有下列特征:
CREATE TABLE lookup<br>(id INT, INDEX USING HASH (id))<br>ENGINE = MEMORY;<br>CREATE TABLE lookup<br>(id INT, INDEX USING BTREE (id))<br>ENGINE = MEMORY;
SUM_OVER_ALL_BTREE_KEYS(max_length_of_key + sizeof(char*) * 4)<br>+ SUM_OVER_ALL_HASH_KEYS(sizeof(char*) * 2)<br>+ ALIGN(length_of_row+1, sizeof(char*))
SET max_heap_table_size = 1024*1024;<br>/* Query OK, 0 rows affected (0.00 sec) */<br><br>CREATE TABLE t1 (id INT, UNIQUE(id)) ENGINE = MEMORY;<br>/* Query OK, 0 rows affected (0.01 sec) */<br><br>SET max_heap_table_size = 1024*1024*2;<br>/* Query OK, 0 rows affected (0.00 sec) */<br><br>CREATE TABLE t2 (id INT, UNIQUE(id)) ENGINE = MEMORY;<br>/* Query OK, 0 rows affected (0.00 sec) */
Memory存储引擎官方论坛: http://forums.mysql.com/list.php?92
分别测试比较了MySQL的InnoDB、MyIsam、Memory三种引擎与.Net DataTable的Insert以及Select性能(柱状图体现了其消耗时间,单位百纳秒,innodb_flush_log_at_trx_commit参数配置为1,每次测试重启了MySQL以避免Query Cache),大至结果如下:
写入10000条记录比较。
读取1000条记录比较。
测试脚本:
/******************************************************<br>MYSQL STORAGE ENGINE TEST<br>http://wu-jian.cnblogs.com/<br>2011-11-29<br>******************************************************/<br><br><br>CREATE DATABASE IF NOT EXISTS test<br> CHARACTER SET 'utf8'<br> COLLATE 'utf8_general_ci';<br>USE test;<br><br><br>/******************************************************<br>1.INNODB<br>******************************************************/<br><br>DROP TABLE IF EXISTS test_innodb;<br>CREATE TABLE IF NOT EXISTS test_innodb (<br><br> id INT UNSIGNED AUTO_INCREMENT COMMENT 'PK',<br> obj CHAR(255) NOT NULL DEFAULT '' COMMENT 'OBJECT',<br><br> PRIMARY KEY (id)<br><br>) ENGINE=INNODB;<br><br><br>/******************************************************<br>2.MYISAM<br>******************************************************/<br><br>DROP TABLE IF EXISTS test_myisam;<br>CREATE TABLE IF NOT EXISTS test_myisam (<br><br> id INT UNSIGNED AUTO_INCREMENT COMMENT 'PK',<br> obj CHAR(255) NOT NULL DEFAULT '' COMMENT 'OBJECT',<br><br> PRIMARY KEY (id)<br><br>) ENGINE=MYISAM;<br><br><br>/******************************************************<br>1.MEMORY<br>******************************************************/<br><br>DROP TABLE IF EXISTS test_memory;<br>CREATE TABLE IF NOT EXISTS test_memory (<br><br> id INT UNSIGNED AUTO_INCREMENT COMMENT 'PK',<br> obj CHAR(255) NOT NULL DEFAULT '' COMMENT 'OBJECT',<br><br> PRIMARY KEY (id)<br><br>) ENGINE=MEMORY;
测试代码:
using System;<br>using System.Data;<br>using MySql.Data.MySqlClient;<br><br>namespace MySqlEngineTest<br>{<br> class Program<br> {<br> 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.";<br> 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;";<br><br> const int LOOP_TOTAL = 10000;<br> const int LOOP_BEGIN = 8000;<br> const int LOOP_END = 9000;<br><br> #region Database Functions<br><br> public static bool DB_InnoDBInsert(string obj)<br> {<br> string commandText = "INSERT INTO test_innodb (obj) VALUES (?obj)";<br> MySqlParameter[] parameters = { <br> new MySqlParameter("?obj", MySqlDbType.VarChar, 255)<br> };<br> parameters[0].Value = obj;<br> if (DBUtility.MySqlHelper.ExecuteNonQuery(SQL_CONN, CommandType.Text, commandText, parameters) > 0)<br> return true;<br> else<br> return false;<br> }<br><br> public static string DB_InnoDBSelect(int id)<br> {<br> string commandText = "SELECT obj FROM test_innodb WHERE id = ?id";<br> MySqlParameter[] parameters = { <br> new MySqlParameter("?id", MySqlDbType.Int32)<br> };<br> parameters[0].Value = id;<br> return DBUtility.MySqlHelper.ExecuteScalar(SQL_CONN, CommandType.Text, commandText, parameters).ToString();<br> }<br><br> public static bool DB_MyIsamInsert(string obj)<br> {<br> string commandText = "INSERT INTO test_myisam (obj) VALUES (?obj)";<br> MySqlParameter[] parameters = { <br> new MySqlParameter("?obj", MySqlDbType.VarChar, 255)<br> };<br> parameters[0].Value = obj;<br> if (DBUtility.MySqlHelper.ExecuteNonQuery(SQL_CONN, CommandType.Text, commandText, parameters) > 0)<br> return true;<br> else<br> return false;<br> }<br><br> public static string DB_MyIsamSelect(int id)<br> {<br> string commandText = "SELECT obj FROM test_myisam WHERE id = ?id";<br> MySqlParameter[] parameters = { <br> new MySqlParameter("?id", MySqlDbType.Int32)<br> };<br> parameters[0].Value = id;<br> return DBUtility.MySqlHelper.ExecuteScalar(SQL_CONN, CommandType.Text, commandText, parameters).ToString();<br> }<br><br> public static bool DB_MemoryInsert(string obj)<br> {<br> string commandText = "INSERT INTO test_memory (obj) VALUES (?obj)";<br> MySqlParameter[] parameters = { <br> new MySqlParameter("?obj", MySqlDbType.VarChar, 255)<br> };<br> parameters[0].Value = obj;<br> if (DBUtility.MySqlHelper.ExecuteNonQuery(SQL_CONN, CommandType.Text, commandText, parameters) > 0)<br> return true;<br> else<br> return false;<br> }<br><br> public static string DB_MemorySelect(int id)<br> {<br> string commandText = "SELECT obj FROM test_memory WHERE id = ?id";<br> MySqlParameter[] parameters = { <br> new MySqlParameter("?id", MySqlDbType.Int32)<br> };<br> parameters[0].Value = id;<br> return DBUtility.MySqlHelper.ExecuteScalar(SQL_CONN, CommandType.Text, commandText, parameters).ToString();<br> }<br><br> #endregion<br><br> #region Test Functions InnoDB<br><br> static void InnoDBInsert()<br> {<br> long begin = DateTime.Now.Ticks;<br> for (int i = 0; i {<br> DB_InnoDBInsert(OBJ);<br> }<br> Console.WriteLine("InnoDB Insert Result: {0}", DateTime.Now.Ticks - begin);<br> }<br><br> static void InnoDBSelect()<br> {<br> long begin = DateTime.Now.Ticks;<br> for (int i = LOOP_BEGIN; i {<br> DB_InnoDBSelect(i);<br> }<br> Console.WriteLine("InnoDB SELECT Result: {0}", DateTime.Now.Ticks - begin);<br> }<br><br> static void MyIsamInsert()<br> {<br> long begin = DateTime.Now.Ticks;<br> for (int i = 0; i {<br> DB_MyIsamInsert(OBJ);<br> }<br> Console.WriteLine("MyIsam Insert Result: {0}", DateTime.Now.Ticks - begin);<br> }<br><br> static void MyIsamSelect()<br> {<br> long begin = DateTime.Now.Ticks;<br> for (int i = LOOP_BEGIN; i {<br> DB_MyIsamSelect(i);<br> }<br> Console.WriteLine("MyIsam SELECT Result: {0}", DateTime.Now.Ticks - begin);<br> }<br><br> static void MemoryInsert()<br> {<br> long begin = DateTime.Now.Ticks;<br> for (int i = 0; i {<br> DB_MemoryInsert(OBJ);<br> }<br> Console.WriteLine("Memory Insert Result: {0}", DateTime.Now.Ticks - begin);<br> }<br><br> static void MemorySelect()<br> {<br> long begin = DateTime.Now.Ticks;<br> for (int i = LOOP_BEGIN; i {<br> DB_MemorySelect(i);<br> }<br> Console.WriteLine("Memory SELECT Result: {0}", DateTime.Now.Ticks - begin);<br> }<br><br> static void DataTableInsertAndSelect()<br> {<br> //Insert<br> DataTable dt = new DataTable();<br> dt.Columns.Add("id", Type.GetType("System.Int32"));<br> dt.Columns["id"].AutoIncrement = true;<br> dt.Columns.Add("obj", Type.GetType("System.String"));<br><br> DataRow dr = null;<br><br> long begin = DateTime.Now.Ticks;<br> for (int i = 0; i {<br> dr = null;<br> dr = dt.NewRow();<br> dr["obj"] = OBJ;<br> dt.Rows.Add(dr);<br> }<br> Console.WriteLine("DataTable Insert Result: {0}", DateTime.Now.Ticks - begin);<br><br> //Select<br> long begin1 = DateTime.Now.Ticks;<br> for (int i = LOOP_BEGIN; i {<br> dt.Select("id = " + i);<br> }<br> Console.WriteLine("DataTable Select Result: {0}", DateTime.Now.Ticks - begin1);<br> }<br><br> #endregion<br><br> static void Main(string[] args)<br> {<br> InnoDBInsert();<br> InnoDBSelect();<br><br> //restart mysql to avoid query cache<br><br> MyIsamInsert();<br> MyIsamSelect();<br><br> //restart mysql to avoid query cache<br><br> MemoryInsert();<br> MemorySelect();<br><br> DataTableInsertAndSelect();<br> }<br><br> }//end class<br>}
.Net Cache读写性能毫无疑问大大领先于数据库引擎
InnoDB写入耗时大概是MyIsam和Memory的5位左右,它的行锁机制必然决定了写入时的更多性能开销,而它的强项在于多线程的并发处理,而本测试未能体现其强项。
三种数据库引擎在SELECT性能上差不多,Memory稍占优。
bitsCN.com