【问题标题】:Proper use of MySQL full text search with SQLAlchemy使用 SQLAlchemy 正确使用 MySQL 全文搜索
【发布时间】:2013-02-04 23:14:25
【问题描述】:

我希望能够在我的一个 SQLAlchemy 映射对象的多个文本字段中进行全文搜索。我还希望我的映射对象支持外键和事务。

我打算使用 MySQL 来运行全文搜索。但是,我知道 MySQL 只能在不支持事务和外键的 MyISAM 表上运行全文搜索。

为了实现我的目标,我计划创建两个表。我的代码将如下所示:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    description = Column(Text)

users_myisam = Table('users_myisam', Base.metadata,
                     Column('id', Integer),
                     Column('name', String(50)),
                     Column('description', Text),
                     mysql_engine='MyISAM')

conn = Base.metadata.bind.connect()
conn.execute("CREATE FULLTEXT INDEX idx_users_ftxt \
              on users_myisam (name, description)")

然后,我会运行这个来搜索:

q = 'monkey'
ft_search = users_myisam.select("MATCH (name,description) AGAINST ('%s')" % q)
result = ft_search.execute()
for row in result: print row

这似乎可行,但我有几个问题:

  1. 我创建两个表来解决我的问题的方法是否合理?有没有标准/更好/更清洁的方法来做到这一点?

  2. 有没有一种 SQLAlchemy 方法来创建全文索引,还是我最好像上面那样直接执行“CREATE FULLTEXT INDEX ...”?

  3. 看起来我在搜索/匹配查询时遇到了 SQL 注入问题。如何选择“SQLAlchemy 方式”来解决这个问题?

  4. 有没有一种干净的方法可以将 users_myisam 选择/匹配项加入到我的用户表中并返回实际的用户实例,因为这是我真正想要的?

  5. 为了使我的 users_myisam 表与我的映射对象用户表保持同步,我是否可以在我的 User 类上使用 MapperExtension,并设置 before_insert、before_update 和 before_delete 方法来更新users_myisam 表是否合适,或者有更好的方法来完成这个?

谢谢, 迈克尔

【问题讨论】:

  • 我知道这可能是不可能的,但是如果你正在启动一个新的应用程序,Postgresql 也有全文索引,没有 MySQL 的愚蠢限制。

标签: python mysql full-text-search sqlalchemy


【解决方案1】:

我创建两个表来解决我的问题的方法是否合理? 有没有标准/更好/更清洁的方法来做到这一点?

我以前从未见过这种用例尝试过,因为重视事务和约束的开发人员倾向于首先使用 Postgresql。我知道在您的特定情况下这可能是不可能的。

有没有一种 SQLAlchemy 方法来创建全文索引,或者我最好 像上面那样直接执行“CREATE FULLTEXT INDEX ...”?

conn.execute() 很好,但如果你想要更集成的东西,你可以使用 DDL() 构造,请阅读http://docs.sqlalchemy.org/en/rel_0_8/core/schema.html?highlight=ddl#customizing-ddl 了解详细信息

看起来我在搜索/匹配查询时遇到了 SQL 注入问题。我该怎么做 选择“SQLAlchemy 方式”来解决这个问题?

注意:此方法适用于 MATCH 同时针对多列 - 如果您只有一列,请更简单地使用 match() 运算符。 p>

基本上你可以使用 text() 构造:

from sqlalchemy import text, bindparam

users_myisam.select(
  text("MATCH (name,description) AGAINST (:value)", 
       bindparams=[bindparam('value', q)])
)

更全面地说,您可以定义一个自定义构造:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import ClauseElement
from sqlalchemy import literal

class Match(ClauseElement):
    def __init__(self, columns, value):
        self.columns = columns
        self.value = literal(value)

@compiles(Match)
def _match(element, compiler, **kw):
    return "MATCH (%s) AGAINST (%s)" % (
               ", ".join(compiler.process(c, **kw) for c in element.columns),
               compiler.process(element.value)
             )

my_table.select(Match([my_table.c.a, my_table.c.b], "some value"))

文档:

http://docs.sqlalchemy.org/en/rel_0_8/core/compiler.html

有没有一种干净的方法可以加入 users_myisam 选择/匹配右后卫 到我的用户表并返回实际的用户实例,因为这是我真正想要的?

你可能应该创建一个 UserMyISAM 类,像 User 一样映射它,然后使用 relationship() 将两个类链接在一起,这样简单的操作是可能的:

query(User).join(User.search_table).\
           filter(Match([UserSearch.x, UserSearch.y], "some value"))

为了让我的 users_myisam 表与我的映射对象保持同步 用户表,我在我的表上使用 MapperExtension 是否有意义 用户类,并设置 before_insert、before_update 和 before_delete 方法来适当地更新 users_myisam 表, 还是有更好的方法来实现这一点?

MapperExtensions 已被弃用,因此您至少可以使用event API,并且在大多数情况下,我们希望尝试在刷新过程之外应用对象突变。在这种情况下,我将使用 User 的构造函数,或者init event,以及一个基本的@validates 装饰器,它将接收用户目标属性的值并将这些值复制到User.search_table

总的来说,如果您一直从其他来源(如 Oreilly 的书)学习 SQLAlchemy,那么它确实已经过时多年了,我将专注于当前的在线文档。

【讨论】:

  • 非常感谢!这非常有帮助。澄清一下,全文搜索本身并不一定要包含在任何事务中,但它正在搜索的对象应该能够与数据模型中的其他对象一起存在于事务中。我使用 MySQL 是因为 Amazon RDS,但这绝不是要求。
  • 请同时描述InnoDB
猜你喜欢
  • 2012-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-12
  • 1970-01-01
  • 1970-01-01
  • 2010-12-29
相关资源
最近更新 更多