Chinaunix首页 | 论坛 | 博客
  • 博客访问: 244827
  • 博文数量: 22
  • 博客积分: 1806
  • 博客等级: 上尉
  • 技术积分: 272
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-16 20:10
文章分类

全部博文(22)

文章存档

2010年(6)

2009年(16)

分类: 数据库开发技术

2010-01-26 21:23:12

 
关系型数据库MysqlKey-value型数据库Mongodb数据插入对比
一,模式设计对比
MongoDb相比于传统的SQL关系型数据库,最大的不同在于它们的模式设计(Schema Design)上的差别,正是由于这一层次的差别衍生出其它各方面的不同。
我们可以简单的认为关系型数据库由数据库、表(table)、记录(record)三个层次概念组成,而在构建一个关系型数据库的时候,工作重点和难点都在数据库表的划分与组织上。一般而言,为了平衡提高存取效率与减少数据冗余之间的矛盾,设计的数据库表都会尽量满足所谓的第三范式。相对的,可以认为MongoDb由数据库、集合(collection)、文档对象(Document-orientedBSON)三个层次组成。MongoDb里的collection对应于关系型数据库里的表。当然,不要期望collection会满足所谓的第三范式,因为它们根本就不在同一个概念讨论之内。类似于表由多条记录组成,集合也包含多个文档对象,虽然说一般情况下,同一个集合内的文档对象具有相同的格式定义,但这并不是必须的,即MongoDb的数据模式是自由的(schema-free、模式自由、无模式)。
以一实例来说,假设我们需要设计一个小型数据库来存储“学生、地址、科目、成绩”这些信息,那么关系型数据库的设计如图1所示,而Key-value型数据库的设计则可能如图2所示。

1、关系型的数据库设计
2Key-value型的数据库设计(直接借用的mongodb官方图)
      对比图1和图2,在关系型的数据库设计里划分出了4个表,而在Key-value型的数据库设计里却只有两个集合。如果说集合与表一一对应的话,那么图2中应该也有4个集合才对,为什么可以把本应该是集合的addressscores直接合入了集合students中?原因就在于在Key-value型的数据库里,数据模式是自由的。
scores来说,在关系型的数据库设计中将其单独成一个表是因为studentscore是一对多的关系,如果将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型数据库的自身特性使得字段的增删改十分容易。
 
二,数据插入性能对比
       以上面给出的应用实例为例子,我们来比较一下mysqlmongodb两者在大量数据插入方面的性能。
       下面给出的是用c++编写的mongodb数据插入示例代码,编译的程序接收一个参数,表示插入的记录数。插入的数据是随机构成的,都是简单的数字。
#include
 
#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
#include
#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.exeinsert.mysql.exe,它们各自实现对mongodb集合db.studentsmysqldb.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已经包含了addresscourse,而mysql只有nameaddressId两个字段:(图略)
 
结论:
       虽然不完全可靠,但上面的数据显示了mongodbmysql两者之间数据插入性能比约为13:29左右。当数据量更大时,这种差别依然如此,我试着继续增加100万条记录,性能对比结果同样为13:29左右,我只试了一次,没有足够的耐心去等待,可以肯定的是就数据插入性能方面,mongodb肯定是要强过mysql,只是为什么差别如此之大,却尚不清楚。
但我猜测原因有这么几点:
第一,   mongodb并未将数据实时写入磁盘而是达到一定数量后批量写入,理由在于:一般情况下,进程mongodb占用的内存只有几M大小,但当执行数据插入时,其所占用的内存持续增长,当达到一定程度时才突然下降。mongodb控制台的显示内容也支持这一猜测。
第二,   mongodb将数据写入多个文件内,而并不是写到同一个文件,多个文件以阿拉伯数字顺序做为后缀,并且对文件大小是逐个翻倍分配磁盘,不知道这有什么理论根据(我能想到的是与‘树’结构相关,也许)。
阅读(6026) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~