一、表模型动态定义方法
最近遇到一个需求场景,需要在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,问题解决了就行。