问题 加速MongoDB中的正则表达式字符串搜索


我正在尝试使用MongoDB来实现自然语言字典。我有一个lexemes集合,每个lexemes都有许多wordforms作为子文档。这是一个单一的lexeme看起来像:

{
    "_id" : ObjectId("51ecff7ee36f2317c9000000"),
    "pos" : "N",
    "lemma" : "skrun",
    "gloss" : "screw",
    "wordforms" : [ 
        {
            "number" : "sg",
            "surface_form" : "skrun",
            "phonetic" : "ˈskruːn",
            "gender" : "m"
        }, 
        {
            "number" : "pl",
            "surface_form" : "skrejjen",
            "phonetic" : "'skrɛjjɛn",
            "pattern" : "CCCVCCVC"
        }
    ],
    "source" : "Mayer2013"
}

目前我有大约4000个lexemes的集合,每个lexemes平均有一个1000个字形的列表(而不是上面的2个)。这意味着我在集合中有效地拥有4,000,000个独特的单词形式,我需要能够在合理的时间内搜索它们。

普通查询看起来像这样:

db.lexemes.find({"wordforms.surface_form":"skrejjen"})

我有一个索引 wordforms.surface_form,这个搜索非常快。 但是,如果我想在搜索中使用通配符,则表现非常糟糕。例如:

db.lexemes.find({"wordforms.surface_form":/skrej/})

需要5分钟(此时我放弃了等待)。如上所述 在这个问题上,已知索引的正则表达式搜索是不好的。我知道在正则表达式搜索中添加^锚点 很有帮助,但它也严重限制了我的搜索功能。即使我愿意做出这样的牺牲,我也注意到响应时间仍然可以根据正则表达式而变化很大。查询

db.lexemes.find({"wordforms.surface_form":/^s/})

需要35秒才能完成。

到目前为止,我所做的最好的结果实际上是当我关闭索引时使用 hint。 在这种情况下,事情似乎有了很大改善。这个查询:

db.lexemes.find({"wordforms.surface_form":/skrej/}).hint('_id_')

需要大约3秒才能完成。

我的问题是,我还能做些什么来改善这些搜索时间吗?虽然它们是,但它们仍然有点慢,我已经在考虑迁移到MySQL以期获得性能。但我真的希望保持Mongo的灵活性并避免RDBMS中所有繁琐的规范化。有什么建议么?您是否认为无论数据库引擎如何,我都会遇到这么慢的文本数据?

我知道Mongo的新事物 文字搜索 功能,但这种(标记化和词干化)的优点在我的情况下是不相关的(更不用说我的语言不受支持)。目前尚不清楚文本搜索是否实际存在 更快 而不是使用正则表达式。


11704
2017-07-30 08:44


起源



答案:


一种可能性是存储您认为可能作为数组元素有用的所有变体 - 不确定这是否可能!

    {
        "number" : "pl",
        "surface_form" : "skrejjen",
        "surface_forms: [ "skrej", "skre" ],
        "phonetic" : "'skrɛjjɛn",
        "pattern" : "CCCVCCVC"
    }

我可能还建议不要在每个单词中存储1000个单词表单,但要将其转换为包含较小的单词。文档越小,MongoDB每次搜索就必须读入内存的次数越少(只要搜索条件不需要完整扫描):

{
    "word": {
        "pos" : "N",
        "lemma" : "skrun",
        "gloss" : "screw",
    },
    "form" : {
        "number" : "sg",
        "surface_form" : "skrun",
        "phonetic" : "ˈskruːn",
        "gender" : "m"
    },
    "source" : "Mayer2013"
}

{
    "word": {
        "pos" : "N",
        "lemma" : "skrun",
        "gloss" : "screw",
    },
    "form" : {
        "number" : "pl",
        "surface_form" : "skrejjen",
        "phonetic" : "'skrɛjjɛn",
        "pattern" : "CCCVCCVC"
    },
    "source" : "Mayer2013"
}

我也怀疑MySQL在搜索随机单词形式时会表现得更好,因为它必须像MongoDB那样进行全表扫描。唯一可以帮助的是查询缓存 - 但是当然,您可以在应用程序中轻松地在搜索UI / API中构建。


9
2017-07-30 09:34



谢谢你的建议!这当然会引入大量冗余信息并使整个集合更大,但如果它增加了搜索响应时间,那么我可能会考虑它。我将运行一些测试,看看是否是这种情况,并在此处发布更新。 - John J. Camilleri


答案:


一种可能性是存储您认为可能作为数组元素有用的所有变体 - 不确定这是否可能!

    {
        "number" : "pl",
        "surface_form" : "skrejjen",
        "surface_forms: [ "skrej", "skre" ],
        "phonetic" : "'skrɛjjɛn",
        "pattern" : "CCCVCCVC"
    }

我可能还建议不要在每个单词中存储1000个单词表单,但要将其转换为包含较小的单词。文档越小,MongoDB每次搜索就必须读入内存的次数越少(只要搜索条件不需要完整扫描):

{
    "word": {
        "pos" : "N",
        "lemma" : "skrun",
        "gloss" : "screw",
    },
    "form" : {
        "number" : "sg",
        "surface_form" : "skrun",
        "phonetic" : "ˈskruːn",
        "gender" : "m"
    },
    "source" : "Mayer2013"
}

{
    "word": {
        "pos" : "N",
        "lemma" : "skrun",
        "gloss" : "screw",
    },
    "form" : {
        "number" : "pl",
        "surface_form" : "skrejjen",
        "phonetic" : "'skrɛjjɛn",
        "pattern" : "CCCVCCVC"
    },
    "source" : "Mayer2013"
}

我也怀疑MySQL在搜索随机单词形式时会表现得更好,因为它必须像MongoDB那样进行全表扫描。唯一可以帮助的是查询缓存 - 但是当然,您可以在应用程序中轻松地在搜索UI / API中构建。


9
2017-07-30 09:34



谢谢你的建议!这当然会引入大量冗余信息并使整个集合更大,但如果它增加了搜索响应时间,那么我可能会考虑它。我将运行一些测试,看看是否是这种情况,并在此处发布更新。 - John J. Camilleri


正如Derick所建议的那样,我在我的数据库中重构了数据,以便我将“wordforms”作为集合而不是“lexemes”下的子文档。 结果实际上更好! 这是一些速度比较。最后一个例子使用 hint故意绕过索引 surface_form,在旧架构中实际上更快。

旧架构 (看到 原始问题

Query                                                              Avg. Time
db.lexemes.find({"wordforms.surface_form":"skrun"})                0s
db.lexemes.find({"wordforms.surface_form":/^skr/})                 1.0s
db.lexemes.find({"wordforms.surface_form":/skru/})                 > 3mins !
db.lexemes.find({"wordforms.surface_form":/skru/}).hint('_id_')    2.8s

新架构 (看到 德里克的答案

Query                                                              Avg. Time
db.wordforms.find({"surface_form":"skrun"})                        0s
db.wordforms.find({"surface_form":/^skr/})                         0.001s
db.wordforms.find({"surface_form":/skru/})                         1.4s
db.wordforms.find({"surface_form":/skru/}).hint('_id_')            3.0s

对我来说,这是一个很好的证据,证明重构的模式可以使搜索更快,并且值得冗余数据(或需要额外的连接)。


7
2017-07-31 06:41