一、表模型動態定義方法
最近遇到一個需求場景,需要在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,問題解決了就行。