为什么这两个函数返回的结果不一样?

问题描述 投票:0回答:1

我正在尝试更新一个功能以提高性能。 对于同一调用,旧函数需要约 32 秒,新函数需要约 350 毫秒,但现在它们没有返回相同的结果,我不确定为什么。

功能参数:

  • md5Hashes:字符串数组
  • 排除的TextId:字符串

该功能应该做什么:

它使用以下猫鼬模式:

const sentenceFeatureSchema = new mongoose.Schema({
    text_id: { type: String, required: true },
    md5_hash: { type: String, required: true },
    offset: { type: Number, required: true },
    length: { type: Number, required: true },
    num_words: { type: Number, required: true },
});

给定一个哈希数组,查询 mongodb 中与给定数组中任何 md5 哈希匹配的所有文档。如果提供了 exceptedTextId,则不包含具有相同 textId 的文档。哈希值和 textid 不是排他性的,这意味着任何文档都可以具有 textid 和哈希值的任意组合(甚至可以有多个文档具有相同的 id 和哈希值,但其他值会有所不同)。

注意:应该只查询需要的数据,因为系统中有很多文档,所以必须避免尝试查询超出我们需要的数据,然后只使用我们需要的数据。

问题

我的新实现没有返回与原始结果相同的结果。我相信新的实施是正确的,而旧的实施则不是,但我可能是错的。不管怎样,我想确切地了解他们返回不同结果的内容和原因。

原创功能

const getMatchingHashSentences = async (md5Hashes, excludedTextId = null) => {
    try {
        const entries = [];

        for (const md5Hash of md5Hashes) {
            const queryConditions = { md5_hash: md5Hash };
            if (excludedTextId) {
                queryConditions.text_id = { $ne: excludedTextId };
            }
            const result = await SentenceFeature.find(queryConditions);
            entries.push(...result);
        }
        logger.debug(`entries length: ${entries.length}`);
        return entries;
    } catch (error) {
        throw error;
    }
};

新功能

我尝试将其变成单个数据库请求。

const getMatchingHashSentences = async (md5Hashes, excludedTextId = null) => {
    try {
        const query = SentenceFeature.find({ md5_hash: { $in: md5Hashes } });

        if (excludedTextId) {
            query.where("text_id").ne(excludedTextId);
        }

        const results = await query.exec();
        return results;
    } catch (error) {
        throw error;
    }
};

编辑

按照要求,这里是演示代码,它将创建 10 个元素并使用这两个函数来查询它们:

演示目录:

mkdir demo && cd demo

准系统 npm 项目:

npm init -y && npm i mongoose mongodb

创建 demo.js:

const mongoose = require("mongoose");

// Define schema

const sentenceFeatureSchema = new mongoose.Schema({
    text_id: { type: String, required: true },
    md5_hash: { type: String, required: true },
    offset: { type: Number, required: true },
    length: { type: Number, required: true },
    num_words: { type: Number, required: true },
});

// Define model
const SentenceFeature = mongoose.model("features", sentenceFeatureSchema);

const getMatchingHashSentences = async (md5Hashes, excludedTextId = null) => {
    try {
        const entries = [];

        for (const md5Hash of md5Hashes) {
            const queryConditions = { md5_hash: md5Hash };
            if (excludedTextId) {
                queryConditions.text_id = { $ne: excludedTextId };
            }
            const result = await SentenceFeature.find(queryConditions);
            entries.push(...result);
        }
        return entries;
    } catch (error) {
        throw error;
    }
};

const getMatchingHashSentences2 = async (md5Hashes, excludedTextId = null) => {
    try {
        const query = SentenceFeature.find({ md5_hash: { $in: md5Hashes } });

        if (excludedTextId) {
            query.where("text_id").ne(excludedTextId);
        }

        const results = await query.exec();
        return results;
    } catch (error) {
        throw error;
    }
};

// Connect to MongoDB
mongoose
    .connect("mongodb://localhost:27017/mydatabase", { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => {
        console.log("Connected to MongoDB");

        const feature = new Feature({
            text_id: "1",
            md5_hash: "h1",
            offset: 0,
            length: 0,
            num_words: 0,
        });

        feature
            .save()
            .then(() => {
                console.log("Entry saved successfully");
            })
            .catch((err) => {
                console.error("Error saving entry:", err);
            });
    })
    .catch((err) => {
        console.error("Error connecting to MongoDB:", err);
    });

async function main() {
    try {
        // Connect to MongoDB
        await mongoose.connect("mongodb://localhost:27017/mydatabase", {
            useNewUrlParser: true,
            useUnifiedTopology: true,
        });
        console.log("Connected to MongoDB");

        // Save 10 features

        for (let i = 1; i <= 10; i++) {
            const feature = new SentenceFeature({
                text_id: `${i}`,
                md5_hash: `h${i}`,
                offset: 0,
                length: 0,
                num_words: 0,
            });
            await feature.save();
            console.log(`Saved Feature ${i}`);
        }

        // Query for saved features
        const oldres = await getMatchingHashSentences(["h1", "h3", "h6"], "3");
        console.log("old:", oldres);

        const newres = await getMatchingHashSentences2(["h1", "h3", "h6"], "3");
        console.log("new:", newres);
    } catch (error) {
        console.error("Error:", error);
    } finally {
        // Close connection
        await mongoose.disconnect();
        console.log("Disconnected from MongoDB");
    }
}

main();

// OUTPUT:

// Saved Feature 1
// Saved Feature 2
// Saved Feature 3
// Saved Feature 4
// Saved Feature 5
// Saved Feature 6
// Saved Feature 7
// Saved Feature 8
// Saved Feature 9
// Saved Feature 10
// old: [
//   {
//     _id: new ObjectId('6605aa98790f41e69f3f966b'),
//     text_id: '1',
//     md5_hash: 'h1',
//     offset: 0,
//     length: 0,
//     num_words: 0,
//     __v: 0
//   },
//   {
//     _id: new ObjectId('6605aa98790f41e69f3f9676'),
//     text_id: '6',
//     md5_hash: 'h6',
//     offset: 0,
//     length: 0,
//     num_words: 0,
//     __v: 0
//   }
// ]
// new: [
//   {
//     _id: new ObjectId('6605aa98790f41e69f3f966b'),
//     text_id: '1',
//     md5_hash: 'h1',
//     offset: 0,
//     length: 0,
//     num_words: 0,
//     __v: 0
//   },
//   {
//     _id: new ObjectId('6605aa98790f41e69f3f9676'),
//     text_id: '6',
//     md5_hash: 'h6',
//     offset: 0,
//     length: 0,
//     num_words: 0,
//     __v: 0
//   }
// ]
// Disconnected from MongoDB

为 mogodb 启动一次性 docker 容器:

docker run --rm -d -p 27017:27017 --name my-mongodb mongo

最后,运行演示:

node demo.js

您会注意到,对于这么小的示例,两个函数的输出是相同的,这是正常的。在对大量数据运行查询时,一定存在一些奇怪的边缘情况导致输出差异。

javascript node.js mongodb express mongoose
1个回答
0
投票

来自 Ruby on Rails 背景,我最初的想法是

if (excludedTextId) {
    query.where("text_id").ne(excludedTextId);
}

不会改变

query
,而是返回包含新条件的新查询。这种非变异行为通常可以防止错误,因为您可以重复使用
query
而无需更改标准。

在您的场景中,这本质上意味着上面的代码是无操作,因为新返回的查询没有保存在变量中,也没有执行。如果这确实是问题,那么简单的解决方法就是用新值替换当前的

query
变量。

let query = ...
// ^ let instead of const

if (excludedTextId) {
    query = query.where("text_id").ne(excludedTextId);
}

所以最终的代码(没有重大改变)应该是这样的:

const getMatchingHashSentences = async (md5Hashes, excludedTextId = null) => {
    try {
        let query = SentenceFeature.find({ md5_hash: { $in: md5Hashes } });

        if (excludedTextId) {
            query = query.where("text_id").ne(excludedTextId);
        }

        const results = await query.exec();
        return results;
    } catch (error) {
        throw error;
    }
};

ps。只是为了给大家一些启发,我个人会写成:

function getMatchingHashSentences(md5Hashes, excludedTextId = null) {
    let query = SentenceFeature.where("md5_hash").in(md5Hashes);
    if (excludedTextId) {
        query = query.where("text_id").ne(excludedTextId);
    }
    return query.exec();
}
  1. 如果您要使用

    .where("text_id").ne(excludedTextId)
    ,那么使用
    .where("md5_hash").in(md5Hashes)
    可能会更干净,否则您将混合 2 种不同的构造。

  2. try/catch
    似乎没有必要,因为您唯一要做的就是重新抛出完全相同的异常。如果您删除
    try/catch
    ,错误/异常会自行进一步向上冒泡,这具有完全相同的结果。

  3. 你只需返回

    .exec()
    返回的promise即可。没有理由等待返回值。

    const results = await query.exec();
    return results;
    // is the same as
    return await query.exec();
    // is the same as*
    return query.exec()
    
    // * assuming you don't handle errors (re-throwing is the same as not handling)
    

    或者不使用 Promise(不使用

    async/await
    ):

    返回query.exec().then(结果=>结果) // 是相同的 返回query.exec()

© www.soinside.com 2019 - 2024. All rights reserved.