sqlalchemy动态定义表模型,以及带来的内存泄露问题的解决

一、表模型动态定义方法

最近遇到一个需求场景,需要在mysql中动态生成存储日志数据的表。我使用的数据库ORM是sqlalchemy,经过查阅文档,找到了如下的实现方法,需要的朋友可以拿去用了,注意查看注释哦。

#!/usr/bin/env python2
# -*- coding:utf-8 -*-
from sqlalchemy import Column, BigInteger, DateTime
from sqlalchemy.ext.declarative import declarative_base


class LogTable(declarative_base()):
    __abstract__ = True  # 加上这个,数据表模型才能被继承
    id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True)
    create_time = Column(DateTime, default=None, index=True)


def defind_table(i):
    table_class = type(
        'LogTableTime%s' % i, (LogTable,), {
            '__table_args__': {
                'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8', 'extend_existing': True
            },  # extend_existing为True时可以覆盖已存在的同名表模型
            '__tablename__': 'interface_log_%s' % i
        }
    )  # 定义名为 'interface_log_%s' % i 的表模型

    # do something ...

    table_class.metadata.clear()  # 清除残留的表对象

defind_table(0)

二、发现内存泄露问题

这里遇到了一个内存泄露的坑,sqlalchemy表对象的命名空间和普通变量的有点不一样。一般来说函数退出的时候会自动回收内部的局部变量,但是这里不行。

我们先注释掉table_class.metadata.clear()这一行,然后执行脚本,可以看到执行结束后的内存中变量的统计信息:

#!/usr/bin/env python2
# -*- coding:utf-8 -*-
# import gc
from guppy import hpy
# from memory_profiler import profile
from sqlalchemy import Column, BigInteger, DateTime
from sqlalchemy.ext.declarative import declarative_base


class LogTable(declarative_base()):
    __abstract__ = True
    id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True)
    create_time = Column(DateTime, default=None, index=True)


# @profile
def defind_table(i):
    table_class = type(
        'LogTableTime%s' % i, (LogTable,), {
            '__table_args__': {
                'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8', 'extend_existing': True
            },
            '__tablename__': 'interface_log_%s' % i
        }
    )  # 定义名为 'interface_log_%s' % i 的表

    # do something ...

    # table_class.metadata.clear()  # 清除残留的表对象

h = hpy()
print h.heap()
for i in range(3000):
    defind_table(i)
print h.heap()
# print gc.collect()

可以看到count列下面的数字变化很大,很多新定义的变量都没有释放,这就是内存泄露的情况,怎么办呢?

三、解决内存泄露问题

在网上查了很多文档都没有找到解决方案,抱着试一试的心态看看declarative_base的源码。

def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
                     name='Base', constructor=_declarative_constructor,
                     class_registry=None,
                     metaclass=DeclarativeMeta):
    ...
    lcl_metadata = metadata or MetaData()
    if bind:
        lcl_metadata.bind = bind

    if class_registry is None:
        class_registry = weakref.WeakValueDictionary()

    bases = not isinstance(cls, tuple) and (cls,) or cls
    class_dict = dict(_decl_class_registry=class_registry,
                      metadata=lcl_metadata)

    if isinstance(cls, type):
        class_dict['__doc__'] = cls.__doc__

    if constructor:
        class_dict['__init__'] = constructor
    if mapper:
        class_dict['__mapper_cls__'] = mapper

    return metaclass(name, bases, class_dict)

通过分析源码中定义的变量,最终我锁定了变量lcl_metadata,继续看MetaData的源码,发现一个特殊的方法:

class MetaData(SchemaItem):
    ...

    def clear(self):
        """Clear all Table objects from this MetaData."""

        dict.clear(self.tables)
        self._schemas.clear()
        self._fk_memos.clear()

看clear方法的说明,可以用它清理数据表的对象,应该就是它了,调用试试看,结果如下:

#!/usr/bin/env python2
# -*- coding:utf-8 -*-
# import gc
from guppy import hpy
# from memory_profiler import profile
from sqlalchemy import Column, BigInteger, DateTime
from sqlalchemy.ext.declarative import declarative_base


class LogTable(declarative_base()):
    __abstract__ = True
    id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True)
    create_time = Column(DateTime, default=None, index=True)


# @profile
def defind_table(i):
    table_class = type(
        'LogTableTime%s' % i, (LogTable,), {
            '__table_args__': {
                'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8', 'extend_existing': True
            },
            '__tablename__': 'interface_log_%s' % i
        }
    )  # 定义名为 'interface_log_%s' % i 的表

    # do something ...

    table_class.metadata.clear()  # 清除残留的表对象

h = hpy()
print h.heap()
for i in range(3000):
    defind_table(i)
print h.heap()
# print gc.collect()

 

 好了,问题解决了,你问我clear方法干了什么,我也不是很清楚,人生苦短,我用Python,问题解决了就行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章