EntiryFramework を使い始めて1年が経過しているが、
データ量の増加でやけに動作が遅くなったことがあった。
原因を調べていると何気なくとあるメンバが呼ばれていることがわかった。
結論
ToArray をうかつにやってはいけない。
(当たり前だろって突っ込まれそうな・・)
検証結果
以下に実験した結果を残してみる。
今回使ったモデル
[Table("SampleTable")] public class UserProfile { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [ForeignKey("RelationId")] public virtual RelationModel Relation { get; set; } [ForeignKey("Relation")] public virtual int RelationId { get; set; } } [Table("RelationModel")] public class RelationModel { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
Select
1000件: 71ms
10000件: 860ms
return db.Users.ToArray().Select(q => q.FirstName).ToArray();
1000件: 50ms
10000件: 65ms
return db.Users.Select(q => q.FirstName).ToArray();
OrderBy
1000件: 108ms
10000件: 842ms
return db.Users.ToArray().OrderBy(q => q.FirstName).Select(q => q.FirstName);
1000件: 38ms
10000件: 108ms
return db.Users.OrderBy(q => q.FirstName).Select(q => q.FirstName).ToArray();
Where(自プロパティ)
1000件: 96ms
10000件: 748ms
return db.Users.ToArray().Where(q => q.FirstName.Contains("1")).Select(q => q.FirstName);
1000件: 39ms
10000件: 61ms
return db.Users.Where(q => q.FirstName.Contains("1")).Select(q => q.FirstName).ToArray();
Where(Relation のプロパティ)
1000件: 169ms
10000件: 1536ms
return db.Users.Include("Relation").ToArray().Where(q => q.Relation.FirstName.Contains("1")).Select(q => q.FirstName);
1000件: 37ms
10000件: 70ms
return db.Users.Where(q => q.Relation.FirstName.Contains("1")).Select(q => q.FirstName).ToArray();
まとめ
ここからは予測だが、EntiryFramework を介してのメモリ展開がボトルネックと思われる。
厳密に言えば SQL でのデータ取得量の違いもあるだろうが、メモリ展開に比べれば大したことないはず。
Relation を含めた ToArray が極端に遅くなったのはこのためだろう。
こんなところにつまづく人はそういないだろうが、
ToArray を呼ばざるを得ない状況もあるかもしれないので、
クエリーを考えたDB設計を考えなければならないと思った事例だった。
- 作者: ミック
- 出版社/メーカー: 翔泳社
- 発売日: 2013/08/07
- メディア: Kindle版
- この商品を含むブログ (1件) を見る