如何开发微型的python orm框架(缓存及路由)

简单介绍下 什么是ORM ?

ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。


他的优点就是用起来很是方便, 但是缺点也明显,就是不支持太复杂的语句、含有Queryset的orm性能不咋地。

Python下的orm没几个选择,Django orm ,peewee ,SQLAlchemy ,Pony Orm 。


文章的原文地址是,http://xiaorui.cc/?p=2154




介绍完ORM后,那么简单描述下,我在写python orm中遇到的问题,确切说不能是问题,应该是一些心得。 

为什么会用ORM来操作数据库 .

如果没有ORM数据库映射模型的话,我们一般是这么玩数据库的。 

use = conn.cursor.execute(‘select * from api_users where id=%s’, ‘123’)


但是如果有orm的话,那么我们只需要下面的友好方式就行了。 

#blog: http://xiaorui.cc
class User(Model):
    __table__ = 'api_users'
    id = IntegerField(primary_key=True)
    name = StringField()
#xiaorui.cc
user = User.get(id=123)

很多时候造轮子,都想按照自己的实现方式,实现出跟别人一样的效果。 比如想做个简单的django rom,peewee 。  就因为这,把自己玩的有些抑郁了。

下面是Django orm的实现方式,说实话django咋实现的,我没去看。  我就自己黑灯瞎火下的。 

#blog: http://xiaorui.cc

user = User()

#直接修改属性
user.id = 1
user.name = 'xiaorui.cc'
user.save()

这样虽然很舒心,但是带来的问题是,要在类里面找到你要修改哪个字段。 

刚才我们写了一个表的模型,当你进行操作的时候,你的父类怎么找到字段关联的对象? 

两个方法,第一个方法是 dir(self.__class__) 找到里面所有的对象,进行扫描,看看谁的属性是数据库类型的,也就是Field这个类的。  一定要过滤,因为类里面还有别的对象。

#blog: http://xiaorui.cc
class Model(Utils):
    def __init__(self, rid=0, **kwargs):
        self.table_name = self.__class__.__name__.lower()
        for name in self.field_names:
            field = getattr(self.__class__, name.replace("`", ""))
            setattr(self, name.replace("`", ""), field.default)
        for key, value in kwargs.items():
            setattr(self, key.replace("`", ""), value)
#        self.debug()

    def debug(self):
        for name in dir(self.__class__):
            print name,'-------',getattr(self.__class__,name)

    @property
    def field_names(self):
        names = []
        for name in dir(self.__class__):
            var = getattr(self.__class__, name.replace("`", ""))
            if isinstance(var, Field):                
                names.append("`%s`"%name)
        return names

    @property
    def field_values(self):
        values = []
        for name in self.field_names:
            value = getattr(self, name.replace("`", ""))
            if isinstance(value, Model):
                value = value.id
            if isinstance(value, str):
                value = value.replace("'", "''")
                try:
                    value = value.decode("gbk")
                except Exception, e:
                    pass
                try:
                    value = value.decode("utf8")
                except Exception, e:
                    pass
            values.append("'%s'" % value)
        return values

那么第二个方法就是,用 self.__dict__ ,缺点是这里也会把你实例化的属性也给塞进去。 当然你还是可以做过滤判断。 

class Field(object):
    pass

class MetaModel(type):
    db_table = None
    fields = {}

    def __init__(cls, name, bases, attrs):
        super(MetaModel, cls).__init__(name, bases, attrs)
        fields = {}
        for key, val in cls.__dict__.iteritems():
            if isinstance(val, Field):
                fields[key] = val
        cls.fields = fields
        cls.attrs = attrs

class Model():
    __metaclass__ = MetaModel
    def save(self):
        #self.t = 'field t'
        insert = 'insert ignore into %s(%s) values (%s);' % (self.db_table, ', '.join(self.__dict__.keys()), ', '.join(['%s'] * len(self.__dict__)))
        print insert

说完了,怎么让父类找到你要修改的字段。 但是上面两种方法都会带来另一个问题度。那么就是第二次save的时候,name字段的值是 xiaorui.cc,按照常理来说,第二次我只是save addr这个字段,name没有,那么name就应该为空 。 这说明了我们没有对类里面的对象进行清理。

class Rui(Model):
    db_table = 'kkk'
    name = Field()
    addr = Field()

r = Rui()
r.name = 'xiaorui.cc'
r.save()
r.addr = 'in baidu'
r.save()

后来我又想,其实完全可以这么用。  这就不用考虑过滤类里面的所有对象了。 

Rui().save(name='xiaorui.cc',addr='beijing')

恩,当然我做的东西更像是ORM及sql语句的映射,其实这也是我想要的。专业的orm还有一个很主要的东西,就是QuerySet,她会把你的从mysql取回来的结果,构建成queryset集合的格式。 虽然费劲多包装了一层,但是对于用户来说,使用方法是相当友好的。   但是我不喜欢QuerySet这种东西,性能真是个问题。  对于,django orm 对比 mysqldb原生sql 性能对比,可以看看我以前的文章。 

http://xiaorui.cc/2015/09/24/%E8%AF%9D%E8%AF%B4django-orm%E6%A8%A1%E5%9E%8B%E4%B8%BA%E4%BB%80%E4%B9%88%E6%AF%94%E5%8E%9F%E7%94%9F%E7%9A%84mysqldb%E6%85%A2/

这里要额外说下缓存及路由的实现,对于缓存其实相对简单, 可以直接选个nosql或者是内存做存储,直接放在python内存中,倒是可以用lru的算法实现旧数据的替换。  那么什么又是路由,所有的路由就是当你分库分表后,你的sql语句就不能像以前那样直接查询了。如果你的分表是按照 id ,那么你的路由就要按照id区间,来定位你应该访问哪个表。 

再说的详细点.

有一个大表,你已经按照uid来水平区切分了。

有两种方式来访问它,最笨的方法就是用union的方法,解决不能用它,因为他的存在,已经否定了分表的意义了。 

(SELECT xx FROM table1 WHERE type=1) union
(SELECT xx FROM table1 WHERE type=1) union
(SELECT xx FROM table1 WHERE type=1) union
(SELECT xx FROM table1 WHERE type=1)

那么比较高效的方法是,通过uid定位到具体的表通过算法实现,比如通过uid和表的数目取模,最后得到的表的位置。例如:uid 为500的用户,和表的数目是5个,那表名就是user_0,

uid为1005的用户,1005%5=0; 那表名就是user_0;
依次类推,通过算法实现,总体思路就是把数据分散存储到不同的表中,减少压力及事务锁带来的麻烦。以前做监控的时候,遇到亿级别的数据,用的是时间区间分表的方法,但原理都是一样的。 这与orm路由已经说的差不多了。 


以前在乐视网开发DBA管理系统时,也造了一个orm轮子….  不要脸的说是颇为强大的orm模块,可惜的是因为别的业务也要做调用,我做了不少的妥协。最后写的跟线上业务有些的耦合了,真心不纯粹 ! 夹杂了基本的QuerySet,LruCache,RPC功能我不打算开源,因为代码质量太差… 虽一直在线上跑着,但我对着个不是很满意. 


     这次我还是决定重写一个比较简单的,只是做SQL语句映射的。 恩,我把简易版的ORM放到github了,功能还有些简单,部分功能还没有完善,比如 order by , group by  ,having 啥的。 

话不多说了,我下面介绍下不太用心写的项目,欢迎批评!!!   当然有兴趣的朋友可以一块搞搞,毕竟这项目本身就很干净。  


项目名字:    simple_orm_python

项目地址:    https://github.com/rfyiamcool/simple_orm_mysql

#coding:utf-8
#blog : http://xiaorui.cc
from simple_orm_mysql import *

class User(Model):
    name = CharField()
    addr = CharField()

if __name__ == "__main__":
    user = User()
    user.addr = "beijing of china"
    user.name = 'fengyun'
    user.save()
    user.addr = "shanghai of china"
    user.save()
    user.get(name='xiaorui.cc',addr="beijing")

对于python开发orm映射对象模型,就讲这么多了。 其实可以举一反三在mongodb中也可以用到。  下次找个时间,给大家分享下基于mongodb的orm模型开发。  话说mongodb的orm开发其实更加的灵活。 

END !!!


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

邮箱地址不会被公开。 必填项已用*标注