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,問題解決了就行。

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