为了证明概念,我已经将5400万条记录加载到mongodb中。我们的目标是研究mongodb的查询速度。
我使用以下类来存储数据:
[BsonDiscriminator("Part", Required = true)]
public class Part
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("pgc")]
public int PartGroupCode { get; set; }
[BsonElement("sc")]
public int SupplierCode { get; set; }
[BsonElement("ref")]
public string ReferenceNumber { get; set; }
[BsonElement("oem"), BsonIgnoreIfNull]
public List<OemReference> OemReferences { get; set; }
[BsonElement("alt"), BsonIgnoreIfNull]
public List<AltReference> AltReferences { get; set; }
[BsonElement("crs"), BsonIgnoreIfNull]
public List<CrossReference> CrossReferences { get; set; }
[BsonElement("old"), BsonIgnoreIfNull]
public List<FormerReference> FormerReferences { get; set; }
[BsonElement("sub"), BsonIgnoreIfNull]
public List<SubPartReference> SubPartReferences { get; set; }
}
我创建了以下索引:
- ref, sc, pgc上的化合物索引
- oem. reform升序索引
- alt.refalt升序索引
- crs.refcrs的升序索引
- old.refold的升序索引
- sub.refsub的升序索引
我执行以下查询来测试性能:
var searchValue = "345";
var start = DateTime.Now;
var result1 = collection.AsQueryable<Part>().OfType<Part>().Where(part => part.ReferenceNumber == searchValue);
long count = result1.Count();
var finish = DateTime.Now;
start = DateTime.Now;
var result2 = collection.AsQueryable<Part>().OfType<Part>().Where(part =>
part.ReferenceNumber.Equals(searchValue) ||
part.OemReferences.Any(oem => oem.ReferenceNumber.Equals(searchValue)) ||
part.AltReferences.Any(alt => alt.ReferenceNumber.Equals(searchValue)) ||
part.CrossReferences.Any(crs => crs.ReferenceNumber.Equals(searchValue)) ||
part.FormerReferences.Any(old => old.ReferenceNumber.Equals(searchValue))
);
count = result2.Count();
finish = DateTime.Now;
start = DateTime.Now;
var result3 = collection.AsQueryable<Part>().OfType<Part>().Where(part =>
part.ReferenceNumber.StartsWith(searchValue) ||
part.OemReferences.Any(oem => oem.ReferenceNumber.StartsWith(searchValue)) ||
part.AltReferences.Any(alt => alt.ReferenceNumber.StartsWith(searchValue)) ||
part.CrossReferences.Any(crs => crs.ReferenceNumber.StartsWith(searchValue)) ||
part.FormerReferences.Any(old => old.ReferenceNumber.StartsWith(searchValue))
);
count = result3.Count();
finish = DateTime.Now;
var regex = new Regex("^345"); //StartsWith regex
start = DateTime.Now;
var result4 = collection.AsQueryable<Part>().OfType<Part>().Where(part =>
regex.IsMatch(part.ReferenceNumber) ||
part.OemReferences.Any(oem => regex.IsMatch(oem.ReferenceNumber)) ||
part.AltReferences.Any(alt => regex.IsMatch(alt.ReferenceNumber)) ||
part.CrossReferences.Any(crs => regex.IsMatch(crs.ReferenceNumber)) ||
part.FormerReferences.Any(old => regex.IsMatch(old.ReferenceNumber))
);
count = result4.Count();
finish = DateTime.Now;
结果不是我所期望的:
- Search 1 on 345 results in: 3 records (00:00:00.3635937)
- 在345个结果中搜索2:58条记录(00:00:00.0671566)
- Search 3 on 345 results in: 6189 records (00:01:17.6638459)
- Search 4 on 345 results in: 6189 records (00:01:17.0727802)
为什么StartsWith查询(3和4)这么慢?StartsWith查询的性能是决定成败的关键。
是否创建了错误的索引?如有任何帮助,不胜感激。
使用mongodb与10gen c#驱动程序
更新:查询从Linq转换为MongoDB查询的方式对性能非常重要。我再次构建相同的查询(如3和4),但使用query对象:
var query5 = Query.And(
Query.EQ("_t", "Part"),
Query.Or(
Query.Matches("ref", "^345"),
Query.Matches("oem.refoem", "^345"),
Query.Matches("alt.refalt", "^345"),
Query.Matches("crs.refcrs", "^345"),
Query.Matches("old.refold", "^345")));
start = DateTime.Now;
var result5 = collection.FindAs<Part>(query5);
count = result5.Count();
finish = DateTime.Now;
这个查询的结果以00:00:00.4522972
返回。查询翻译为command: { count: "PSG", query: { _t: "Part", $or: [ { ref: /^345/ }, { oem.refoem: /^345/ }, { alt.refalt: /^345/ }, { crs.refcrs: /^345/ }, { old.refold: /^345/ } ] } }
与查询3和4相比,差异很大:command: { count: "PSG", query: { _t: "Part", $or: [ { ref: /^345/ }, { oem: { $elemMatch: { refoem: /^345/ } } }, { alt: { $elemMatch: { refalt: /^345/ } } }, { crs: { $elemMatch: { refcrs: /^345/ } } }, { old: { $elemMatch: { refold: /^345/ } } } ] } }
那么为什么查询3和4没有使用索引?
从索引文档中:
每个查询,包括更新操作,使用且仅使用一个索引。
换句话说,MongoDB不支持索引交集。因此,创建大量索引是没有意义的,除非有查询使用这个索引并且只使用这个索引。此外,确保在这里调用正确的Count()
方法。如果您调用链接到对象扩展(IEnumerable
的Count()
扩展而不是MongoCursor
的Count
,它实际上必须获取和水合物所有对象)。
可能更容易把这些放在一个多键索引中,像这样:
{
"References" : [ { id: new ObjectId("..."), "_t" : "OemReference", ... },
{ id: new ObjectId("..."), "_t" : "CrossReferences", ...} ],
...
}
,其中References.id
被索引。现在,查询db.foo.find({"References.id" : new ObjectId("...")})
将自动搜索引用数组中的任何匹配项。由于我假设必须区分不同类型的引用,因此使用鉴别器使驱动程序能够支持多态反序列化是有意义的。在c#中,你可以这样声明
[BsonDiscriminator(Required=true)]
[BsonKnownTypes(typeof(OemReference), typeof(...), ...)]
class Reference { ... }
class OemReference : Reference { ... }
驱动程序将自动序列化名为_t
的字段中的类型名称。如果需要,这种行为可以根据您的需要进行调整。
还要注意,缩短属性名称将减少存储需求,但不会影响索引大小。