关系型数据库Mysql与Key-value型数据库Mongodb数据插入对比
一,模式设计对比
MongoDb相比于传统的SQL关系型数据库,最大的不同在于它们的模式设计(Schema Design)上的差别,正是由于这一层次的差别衍生出其它各方面的不同。
我们可以简单的认为关系型数据库由数据库、表(table)、记录(record)三个层次概念组成,而在构建一个关系型数据库的时候,工作重点和难点都在数据库表的划分与组织上。一般而言,为了平衡提高存取效率与减少数据冗余之间的矛盾,设计的数据库表都会尽量满足所谓的第三范式。相对的,可以认为MongoDb由数据库、集合(collection)、文档对象(Document-oriented、BSON)三个层次组成。MongoDb里的collection对应于关系型数据库里的表。当然,不要期望collection会满足所谓的第三范式,因为它们根本就不在同一个概念讨论之内。类似于表由多条记录组成,集合也包含多个文档对象,虽然说一般情况下,同一个集合内的文档对象具有相同的格式定义,但这并不是必须的,即MongoDb的数据模式是自由的(schema-free、模式自由、无模式)。
以一实例来说,假设我们需要设计一个小型数据库来存储“学生、地址、科目、成绩”这些信息,那么关系型数据库的设计如图1所示,而Key-value型数据库的设计则可能如图2所示。
图1、关系型的数据库设计
图2、Key-value型的数据库设计(直接借用的mongodb官方图)
对比图1和图2,在关系型的数据库设计里划分出了4个表,而在Key-value型的数据库设计里却只有两个集合。如果说集合与表一一对应的话,那么图2中应该也有4个集合才对,为什么可以把本应该是集合的address和scores直接合入了集合students中?原因就在于在Key-value型的数据库里,数据模式是自由的。
以scores来说,在关系型的数据库设计中将其单独成一个表是因为student与score是一对多的关系,如果将score合入student表,那么就必须预留最多可能的字段,这会存在浪费,并且当以后新增一门课程时扩展困难,因此一般都会将score表单独出来。而对于Key-value型的数据库就不同了,其scores字段就是一个BSON,该BSON可以只有一个for_course,也可以有两个、三个、任意个for_course,其固有的模式自由特性使得它可以将score包含在内而无需另建一个score集合。
对于与student为一对一关系的address表也可以直接合入student,无需担心address的扩展性,当以后需要给address新增一个province字段,直接在数据插入时加上这个值即可。
当然,对于与student成多对多关系course表,为了减少数据冗余,可以将course建立为一个集合,同关系型的数据库设计中类似。
对比于关系型的数据库,在Key-value型的数据库里将数据合入一起有几大好处:首先,数据检索时没有了进行表间连接(join)的巨大开销(虽然目前MongoDb中没有join的概念);其次,合入一起的数据在磁盘上的存放也更容易在一起,因此数据的读取\写入都更快速。另外,无需担心扩展性问题,Key-value型数据库的自身特性使得字段的增删改十分容易。
二,数据插入性能对比
以上面给出的应用实例为例子,我们来比较一下mysql与mongodb两者在大量数据插入方面的性能。
下面给出的是用c++编写的mongodb数据插入示例代码,编译的程序接收一个参数,表示插入的记录数。插入的数据是随机构成的,都是简单的数字。
#include <iostream>
#include "client/dbclient.h"
#pragma comment(lib, "mongoclient.lib")
#pragma comment(lib, "wsock32.lib")
using namespace std;
using namespace mongo;
int main( int argc, const char **argv ) {
int i, r;
clock_t start, finish;
string errmsg;
string table = "db.students";
int record = 100000;
DBClientConnection conn;
if(argc > 1) {
record = atoi(argv[1]);
}
// connect db server
if (!conn.connect(string("127.0.0.1:55555"), errmsg)) {
cout << "couldn't connect to server:" << errmsg << endl;
return -1;
}
// create index
conn.ensureIndex(table, fromjson("{name:1}"));
// insert test data
start = clock();
srand(start);
for (i = 0; i < record; i ++) {
r = rand(); // use random number
BSONObjBuilder query;
query << GENOID << "name" << r;
BSONObj addr = BSON("address" << i << "city" << i << "state" << i << "postalCode" << i);
query.append("address", addr);
BSONObj scores = BSON("for_course" << r << "grade" << r%10);
query.append("scores", scores);
conn.insert(table, query.obj());
}
finish = clock();
cout << "time:" << (double)(finish - start) / CLOCKS_PER_SEC << "s" << endl;
cout << "insert finished" << endl;
// waiting
cin >> errmsg;
return 0;
}
|
同样,用c++编写的mysql数据插入示例代码如下所示:
#include <iostream>
#include <time.h>
#include "database.h"
using namespace std;
using namespace database;
int main(int argc, char* argv[])
{
string host = "127.0.0.1,811";
string dbname = "db";
string user = "root";
string password = "sinfors";
string table = "Students";
CDatabase mysql;
int i, r;
int record = 100;
clock_t start, finish;
char query[512];
if(argc > 1) {
record = atoi(argv[1]);
}
// connect db server
if (!mysql.open(host.c_str(), dbname.c_str(), user.c_str(), password.c_str()))
{
printf("open database error(%s)", dbname.c_str());
return -1;
}
// insert test data
start = clock();
for (i = 0; i < record; i ++) {
r = rand(); // use random number
_snprintf_s(query, sizeof(query),
"INSERT INTO %s VALUES(NULL, '%u', %u)", table.c_str(), r,r);
mysql.exec(query);
}
finish = clock();
cout << "time:" << (double)(finish - start) / CLOCKS_PER_SEC << "s" << endl;
cout << "insert finished" << endl;
// waiting
cin >> query;
}
|
编译上面的代码(当然,还需要其它库、做设置等等)后获得两个exe文件insert.mongo.exe和insert.mysql.exe,它们各自实现对mongodb集合db.students与mysql表db.Students的数据插入操作。下面开始测试,首先是mongodb数据库,首先运行mongodb数据库服务端,然后执行我们的insert.mongo.exe程序进行数据插入测试,连续执行三次数据插入操作,每次10万条数据:
服务端情况:
执行结果情况:(图略)
即,insert.mongo.exe程序每次10万条数据,连续执行三次数据插入操作分别用的时间为:
12.485秒
12.375秒
12.625秒
平均用时12.495秒。
接着是mysql数据库情况,我们直接使用数据中心的mysql服务,建立相应的数据库以及数据表即可:
create database db;
use db;
create table Students(
id int unsigned auto_increment not null,
name varchar(128) not null,
addressId int unsigned,
index index_name(name),
primary key (id)
)ENGINE=INNODB;
|
执行结果情况:(图略)
可以看到,同样的数据量,insert.mysql.exe程序进行数据插入操作发费的时间明显较多,分别为:
28.375秒
28.36秒
29.921秒
平均用时28.885秒。
另外,还有一个事实那就是上面插入的数据,mongodb已经包含了address与course,而mysql只有name与addressId两个字段:(图略)
结论:
虽然不完全可靠,但上面的数据显示了mongodb与mysql两者之间数据插入性能比约为13:29左右。当数据量更大时,这种差别依然如此,我试着继续增加100万条记录,性能对比结果同样为13:29左右,
我只试了一次,没有足够的耐心去等待,可以肯定的是就数据插入性能方面,mongodb肯定是要强过mysql,只是为什么差别如此之大,却尚不清楚。
但我猜测原因有这么几点:
第一, mongodb
并未将数据实时写入磁盘而是达到一定数量后批量写入,理由在于:一般情况下,进程mongodb占用的内存只有几M大小,但当执行数据插入时,其所占用的内存持续增长,当达到一定程度时才突然下降。mongodb控制台的显示内容也支持这一猜测。
第二, mongodb
将数据写入多个文件内,而并不是写到同一个文件,多个文件以阿拉伯数字顺序做为后缀,并且对文件大小是逐个翻倍分配磁盘,不知道这有什么理论根据(我能想到的是与‘树’结构相关,也许)。