CosmosDB:Linq与SqlQuerySpec在查询CosmosDB时的性能对比

6

我经常在查询CosmosDB特定文档时执行LINQ谓词。但是,今天我用10万个以上的文档填满了我的CosmosDB,性能非常慢。由于Azure门户中的SQL查询明显更快,因此我尝试使用SqlQuerySpec。哇!它运行得更快。

有人能告诉我使用CosmosDB的Linq谓词时发生了什么,并且为什么会减慢我的查询吗?

以下代码用于获取文档的方法。 注意:在这种情况下,id是分区键。

        var collectionUri = UriFactory.CreateDocumentCollectionUri(CDBdatabase, CDBcollection);

        var sqlStatement = new SqlQuerySpec
        {
            QueryText = "SELECT * FROM c where c.id = @id",
            Parameters = new SqlParameterCollection()
                {
                          new SqlParameter("@id", consumerId),
                },
        };

        IDocumentQuery<T> query = documentClient.CreateDocumentQuery<T>(
            collectionUri,
            sqlStatement,
            .AsDocumentQuery();

        List<ConsumerDetails> results = new List<ConsumerDetails>();
        while (query.HasMoreResults)
        {
            results.AddRange(await query.ExecuteNextAsync<ConsumerDetails>());
        }

        return results.FirstOrDefault();

相比之下,速度较慢的代码:

        return documentClient.CreateDocumentQuery<ConsumerDetails>(
            collectionUri,
            .Where(f => f.Id == consumerId).AsEnumerable().FirstOrDefault();

1
虽然略微与您的问题无关,但是如果您真正是通过文档 ID 进行查询,那么ReadDocument() 比查询更快,始终如一(例如,对于 1KB 文档,需要 1 RU),因为它不需要经过查询引擎。 - David Makogon
你可以在从LINQ链中获取的IQueryable上调用ToString(),并检查手动构建查询与来自CosmosDB linq提供程序的查询之间的差异。 - Vitaliy Kalinin
好的,要给出一个好的答案还有几个细节需要补充。您能否请发布一下您的“ConsumerDetails” DTO是什么样子的?您是否尝试在LINQ方法中将分区键值作为“FeedOptions”对象的一部分提供?您是否尝试在LINQ查询上执行.AsDocumentQueryExecuteNextAsync操作? - Nick Chapsas
1个回答

2
答案在于你查询的方式。
你发布的两个代码片段并不相同。
为了让它们相同,第二个代码片段应该像这样:
```html 代码片段 ```
var collectionUri = UriFactory.CreateDocumentCollectionUri(CDBdatabase, CDBcollection);

var query = documentClient.CreateDocumentQuery<ConsumerDetails>(
    collectionUri)
    .Where(f => f.Id == consumerId)
    .AsDocumentQuery();

List<ConsumerDetails> results = new List<ConsumerDetails>();
while (query.HasMoreResults)
{
    results.AddRange(await query.ExecuteNextAsync<ConsumerDetails>());
}

return results.FirstOrDefault();

在这两种情况下,你将执行针对CosmosDB的SQL查询。然而,在LINQ的情况下,一个LINQ翻译器将会启动,将你的表达式转换为SQL查询语句。
此外,在SQL示例中,因为你直接指向小写的id(即CosmosDB的id),这也是分区键,CosmosDB将识别并将查询从跨分区查询限制为特定于分区的查询,使其更快、更便宜。.Where(f => f.Id == consumerId)(其中大写的Id)一旦通过LINQ提供程序进行转换,就会被翻译成SELECT * FROM c where c.Id = consumerId,除非有一个JsonAttribute("id")装饰了Id属性。这意味着你需要在查询的FeedOptions中提供PartitionKey值。

1
你好,我已经根据您和@David Mokogon(在上面的评论中)的反馈更新并比较了我的代码。我现在意识到这不完全是相同的逻辑。是的,我也忘了提到我确实使用JsonAttributes作为ID。然而,这带来了我以下的问题:通过对AsEnumerable()执行LINQ查询与执行AsDocumentQuery + ExecuteNextAsync有性能问题吗?在AsEnumerable下面发生了什么,关于我何时/如何向CosmosDB发送请求? - Lud1212
当然有。AsEnumerable().FirstOrDefault();会同步获取所有与谓词匹配的文档,然后获取第一个或默认值。AsDocumentQuery + ExecuteNextAsync将使用CosmosDB的适当异步分页,以便您的应用程序不会被锁定。 - Nick Chapsas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接