用MySQL数据库来支持Schemaless的数据库存储方案

在PyCon上有童鞋提供了一个类似概念的分享,不过不大适合一般类型的互联网项目,感觉有点过于另类。不过我实现这个方案是在看到PyCon的分享之前。算是同样的诉求不同的实现方式吧。且我这里只是实现了一个数据访问的组件而不是Server。

首先本文的方法来自FriendFeed分享的如何使用MySQL数据库的分享。简而言之就是把Python对象直接dumps后zip压缩存储在MySQL一个字段里。这样不就Schemaless了么?存什么数据类型,类什么结构,MySQL都不需要知道,加个属性什么的都不需要修改数据库表结构,对于业务快速变更、快速增长的互联网业务来说再合适不过了。访问对象直接通过主键查询,快速直接。but,查询怎么办?有的童鞋可能会问。OK,查询这事得分两说,如果是简单的检索,可以通过建索引表的方式来解决,或者呢用外部的索引,比如lucent,还能全文检索哦。现在而今眼目下我实现了索引表索引的方式,因为外部的索引方式比较千奇百怪,所以如果需要可以根据具体情况自己来写一个,反正实现相应的几个方法就行。

直接上一个例子来说明。假设要实现一个blog,需要存blog的信息,先定义一个blog的模型类(需要import什么大家自动脑补)

1 class Blog(DynamicBase):
2 title=Column(unicode,max_length=200)
3 content=Column(unicode)
4 post_date=Column(datetime.datetime,db_index=True)
5 auther=FkColumn(User)
6 class Meta:
7 table_name="blogs"
8 connection=connections[DB]

这个connection是因为我还没想好如何能无缝结合到Django中又能兼顾脱离Django独立使用的暂时措施,完成版会去掉

如果在使用django的话只需要 python manage.py shell 然后 Blog.objects.create_table()

这个时候会自动创建模型定义的表和索引表

数据表 blogs:

CREATE TABLE `blogs` (
`id` int(11) NOT NULL,
`object` varbinary(20000) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 同时建立两个索引表

CREATE TABLE `blog_idx_post_date` (
`id` int(10) unsigned NOT NULL,
`post_date` DATETIME NOT NULL,
PRIMARY KEY (`id`),
INDEX `idx_blogs_by_post_date` (`post_date`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

CREATE TABLE `blog_idx_auther` (
`id` int(10) unsigned NOT NULL,
`auther` INT NOT NULL,
PRIMARY KEY (`id`),
INDEX `idx_blogs_by_auther` (`auther`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

这个时候,可以通过 Blog.objects.create(title=u"标题",content=u"内容",post_date=datetime.datetime.now(),auther=user) 或者Blog.objects.create(title=u"标题",content=u"内容",post_date=datetime.datetime.now(),auther=user.id)

就能够创建一个Blog的对象。这个时候blogs表和两个索引表都会插入数据。不过blogs表中的object列是人类无法理解的火星文..........

通过id直接获取对象  Blog.objects.get(1),根据索引获取  Blog.objects.auther.query(auther=user.id) 或者  Blog.objects.auther.query(auther=user)

这个会生成SQL,SELECT `id` FROM  `blog_idx_auther` WHERE `auther`=%s 然后取出match到对象的id列表,然后遍历id列表,通过 Blog.objects.get(id)获得的对象列表。

Blog.objects.get(id)的时候是有对象缓存的(现阶段通过redis实现),所以经过测试,速度是靠谱的。而相对MangoDB来说,MySQL的数据存储也更加靠谱一点,所以相比换现在而今眼目下还不怎么靠谱的mangodb来作为主存储来说,基于MySQL的Schemaless方案还是相对靠谱的。

 

由于现在这个东西还是处于在项目中孵化的阶段还没有能够达到可以独立开源出来供大家娱乐的程度,所以请大家对这个方案多提意见建议咯,源代码估计能够在春节后达到能够公布出来见人的阶段 



posted on 2011-12-11 23:39 亚历山大同志 阅读(1273) 评论(6) 编辑 收藏

评论

#1楼 2011-12-12 10:02 felinx      

不错的分享,bret taylor那篇文章影响了好多人啊,如果可以剥离出来不依赖于django,另外把那篇文章中提到的datastore里面的索引和分库管理实现了,相信会有很多人来用,合作一起搞搞吧。  回复 引用 查看   

#2楼 2011-12-12 20:08 蛙蛙王子      

挺好的,道理上是能行的通的,还用了元类,还飘逸。。。
我觉得不要和django结合的太紧密了,最好能兼容pymongo的api,这样大家学起来也方便。
 回复 引用 查看   

#3楼 2011-12-12 20:49 Ricky Lee      

想法挺新的,但请教lz几个问题:
1、如果表已经在线上用了并且有数据,那后面如果要加索引,你打算如何保证数据的一致性?如果同时还有并发更新的话;
2、是不是有数据冗余造成的空间浪费?
3、从开发的角度上看,易用性是不是下降了?写代码的时候还需要保证几个表的一致性之类的,如果是需求经常变更的,修改起来很不方便
 回复 引用 查看   

#4楼[楼主] 2011-12-14 17:09 亚历山大同志      

@Ricky Lee
1。加索引会提供根据现有数据重建索引的方法,因为只需要全部select出来重新insert一次
2。确实会有冗余产生,不过存储的时候zip压缩过,互相抵消一下影响
3。写代码的时候因为有model所以代码中的一致性是很好的,需求变更就改models,如果没有动到索引直接能够自动适应变化。这个项目的目的就是用model来消除schemeless中纯字典来存数据造成的问题,且同时也要解决这种存储方式造成的要操作多表的问题,所以数据一致性是这个库本身要解决的问题。使用的时候不用管一致性了。至于易用性,这里有个强迫的行为就是强迫用户必须通过索引来取数据列表,这样可以防止写出低效的数据访问代码来
 回复 引用 查看   

#5楼 2011-12-14 18:05 Ricky Lee      

@亚历山大同志

全部select出来再重新insert有可能存在不一致状态,除非锁表。比如你正在select,这时候有用户insert或update了一条记录,那么索引表中是可能缺少或多出记录的。

单条记录的zip压缩效果可能不会很明显,因为表的记录与记录之间存在冗余信息的可能性更大,除非记录中文本占的比例较大,zip压缩的效果才好。
 回复 引用 查看   

#6楼[楼主] 2011-12-17 01:39 亚历山大同志      

@Ricky Lee
在线上系统新建索引不需要锁表也不会存在不一致的情况,因为不会直接select * from table,而是select * from table where id<当前最大id,然后顺序自己insert就行了,而新近进入的数据也能插入索引表,而索引表的顺序跟插入顺序是无关的。so,不一致的情况是不会出现的。

另外这里的单条记录也许就不是以前的单条数据库记录了,能塞进去的都塞进去,一个大对象,picle后有个50%的压缩率还是很占便宜了
 回复 引用 查看   

导航

公告


放一首适合飚车的音乐,听这个开车会不知不觉的加速
昵称:亚历山大同志
园龄:5年1个月
荣誉:推荐博客
粉丝:117
关注:0
<2011年12月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

统计

搜索

 
 

常用链接

最新随笔

我的标签

随笔分类(128)

随笔档案(134)

相册

朋友的Blog

同事的Blog

积分与排名

最新评论

阅读排行榜

评论排行榜

推荐排行榜