【问题标题】:Dynamically setting __tablename__ for sharding in SQLAlchemy?在 SQLAlchemy 中动态设置 __tablename__ 进行分片?
【发布时间】:2013-10-10 10:19:14
【问题描述】:

为了处理不断增长的数据库表,我们对表名进行分片。所以我们可以有这样命名的数据库表:

table_md5one
table_md5two
table_md5three

所有表都具有完全相同的架构。

我们如何使用 SQLAlchemy 并为对应的类动态指定 tablename?看起来 declarative_base() 类需要预先指定 tablename

最终将有太多的表来手动指定来自父/基类的派生类。我们希望能够构建一个可以动态设置表名的类(可能作为参数传递给函数。)

【问题讨论】:

    标签: python mysql orm sqlalchemy


    【解决方案1】:

    Augmenting the Base 中,您可以找到一种使用自定义Base 类的方法,例如,该类可以动态计算__tablename__ 属性:

    class Base(object):
        @declared_attr
        def __tablename__(cls):
            return cls.__name__.lower()
    

    这里唯一的问题是我不知道你的哈希来自哪里,但这应该是一个很好的起点。

    如果您不是所有表都需要此算法,而只需要其中一个,您可以在对分片感兴趣的表上使用declared_attr

    【讨论】:

    • 如果您尝试在模型定义而不是声明性基类上执行此操作,则它不正确并抛出 InvalidRequestError
    • 添加了一个编辑,但以防万一编辑不被接受,用于扩充基础的更新 URL 不再有效。部分现已在此处提供:Augmenting the Base
    【解决方案2】:

    您可以使用通常的 declarative_base 并进行闭包来设置表名,而不是使用命令式创建 Table 对象:

    def make_class(Base, table_name):
        class User(Base):
            __tablename__ = table_name
            id = Column(Integer, primary_key=True)
            name= Column(String)
    
        return User
    
    Base = declarative_base()
    engine = make_engine()
    custom_named_usertable = make_class(Base, 'custom_name')
    Base.metadata.create_all(engine)
    
    session = make_session(engine)
    new_user = custom_named_usertable(name='Adam')
    session.add(new_user)
    session.commit()
    session.close()
    engine.dispose()
    

    【讨论】:

      【解决方案3】:

      您可以编写一个带有 tablename 参数的函数,并通过设置适当的属性发回该类。

      def get_class(table_name):
      
         class GenericTable(Base):
      
             __tablename__ = table_name
      
             ID= Column(types.Integer, primary_key=True)
             def funcation(self):
              ......
         return GenericTable
      

      然后您可以使用以下方法创建表:

      get_class("test").__table__.create(bind=engine)  # See sqlachemy.engine
      

      【讨论】:

      • 在我看来,这似乎是解决这个问题的最pythonic和最有效的解决方案。
      • 我从接受的答案改为这个,但测试的时间成本是 100 倍 ....
      【解决方案4】:

      因为我坚持使用由给定参数动态指定的 __tablename__ 声明性类,经过数天的其他解决方案失败和数小时的 SQLAlchemy 内部研究后,我想出了以下解决方案,我认为它简单、优雅且无竞争条件。

      def get_model(suffix):
          DynamicBase = declarative_base(class_registry=dict())
      
          class MyModel(DynamicBase):
              __tablename__ = 'table_{suffix}'.format(suffix=suffix)
      
              id = Column(Integer, primary_key=True)
              name = Column(String)
              ...
      
          return MyModel
      

      因为他们有自己的class_registry,你不会收到这样的警告:

      这个声明性基础已经包含一个与 mypackage.models.MyModel 具有相同类名和模块名的类,并将在字符串查找表中被替换。

      因此,您将无法通过字符串查找从其他模型中引用它们。但是,将这些即时声明的模型用于外键也可以很好地工作:

      ParentModel1 = get_model(123)
      ParentModel2 = get_model(456)
      
      class MyChildModel(BaseModel):
          __tablename__ = 'table_child'
      
          id = Column(Integer, primary_key=True)
          name = Column(String)
          parent_1_id = Column(Integer, ForeignKey(ParentModel1.id))
          parent_2_id = Column(Integer, ForeignKey(ParentModel2.id))
          parent_1 = relationship(ParentModel1)
          parent_2 = relationship(ParentModel2)
      

      如果您只使用它们来查询/插入/更新/删除而不留下任何引用,例如来自另一个表的外键引用,它们、它们的基类以及它们的 class_registry 将被垃圾收集,因此不会留下任何痕迹。

      【讨论】:

        【解决方案5】:

        试试这个

        import zlib
        
        from sqlalchemy.ext.declarative import declarative_base
        from sqlalchemy import Column, Integer, BigInteger, DateTime, String
        
        from datetime import datetime
        
        BASE = declarative_base()
        ENTITY_CLASS_DICT = {}
        
        
        class AbsShardingClass(BASE):
        
            __abstract__ = True
        
        def get_class_name_and_table_name(hashid):
            return 'ShardingClass%s' % hashid, 'sharding_class_%s' % hashid
        
        def get_sharding_entity_class(hashid):
            """
            @param hashid: hashid
            @type hashid: int
            @rtype AbsClientUserAuth
            """
        
            if hashid not in ENTITY_CLASS_DICT:
                class_name, table_name = get_class_name_and_table_name(hashid)
                cls = type(class_name, (AbsShardingClass,),
                           {'__tablename__': table_name})
                ENTITY_CLASS_DICT[hashid] = cls
        
            return ENTITY_CLASS_DICT[hashid]
        
        cls = get_sharding_entity_class(1)
        print session.query(cls).get(100)
        

        【讨论】:

          【解决方案6】:

          好的,我们使用自定义 SQLAlchemy 声明而不是声明性声明。

          所以我们像这样创建一个动态表对象:

          from sqlalchemy import MetaData, Table, Column
          
          def get_table_object(self, md5hash):
              metadata = MetaData()
              table_name = 'table_' + md5hash
              table_object = Table(table_name, metadata,
                  Column('Column1', DATE, nullable=False),
                  Column('Column2', DATE, nullable=False)
              )
              clear_mappers()
              mapper(ActualTableObject, table_object)
              return ActualTableObject
          

          其中 ActualTableObject 是映射到表的类。

          【讨论】:

          • 这是我过去所做的,我也希望看到不同的解决方案。
          • @jkmacc @Suman 我添加了一个带有自定义 Base 类的解决方案,这可能会有所帮助,具体取决于用例。
          • @jkmacc 试试我的解决方案。
          • 分表有内置解决方案吗?
          • 来自文档:“clear_mappers() 不适合正常使用,因为在非常特定的测试场景之外实际上没有有效的用法。” docs.sqlalchemy.org/en/13/orm/…
          猜你喜欢
          • 1970-01-01
          • 2019-07-05
          • 2013-01-07
          • 2021-11-09
          • 1970-01-01
          • 2011-07-24
          • 1970-01-01
          • 1970-01-01
          • 2015-07-05
          相关资源
          最近更新 更多