我有一个名为“申请”的表,用于跟踪候选人对职位空缺提出的申请。现在,我需要为 HR 编写一个统计查询,当 HR 登录时,我应该带上申请该 HR 创建的职位空缺的候选人。由于我的查询中同时存在过滤器,因此我还必须包含与候选人相关的其他表。然而,我的查询速度很慢,并且消耗了大量的 RAM。我的 16 GB RAM Linux 服务器在运行该项目 1-2 小时后就被填满并崩溃了。当我检查服务器时,我发现仅这个查询就不断消耗 RAM。您认为我可以如何优化这个,或者查询可能有什么问题?此外,还有+400,000份申请和+100,000名候选人
var entities = _context.Applications
.Include(p => p.Candidate)
.ThenInclude(p => p.CandidateIndustryFields)
.ThenInclude(p => p.DesiredField)
.Include(p => p.Vacancy)
.Include(p => p.Candidate)
.ThenInclude(p => p.CandidateIndustrySubFields)
.ThenInclude(p => p.SubField)
.Include(p => p.Candidate)
.ThenInclude(x => x.CandidateSkills)
.ThenInclude(x => x.Skill)
.Include(p => p.Candidate)
.ThenInclude(x => x.CandidateBackgrounds)
.ThenInclude(x => x.WorkExperienceType)
.Where(m => (m.Vacancy.RecruiterId == userId) && (m.StatusId == searchParameter.StatusId || searchParameter.StatusId == null)
&& (string.IsNullOrEmpty(searchParameter.Name) || m.Candidate.Name.Contains(searchParameter.Name))
&& (string.IsNullOrEmpty(searchParameter.Surname) || m.Candidate.Surname.Contains(searchParameter.Surname))
&& (string.IsNullOrEmpty(searchParameter.PassportFin) || m.Candidate.CandidatePassports.Any(p => p.PassportFin == searchParameter.PassportFin.ToUpper()))
&& (string.IsNullOrEmpty(searchParameter.Patronymic) || m.Candidate.FatherName.Contains(searchParameter.Patronymic))
&& (searchParameter.Gender == null || m.Candidate.GenderId == searchParameter.Gender)
&& (searchParameter.MaritalStatus == null || m.Candidate.MaritalStatusId == searchParameter.MaritalStatus)
&& (searchParameter.MilitaryStatus == null || m.Candidate.MillitaryStatusId == searchParameter.MilitaryStatus)
&& (searchParameter.HasPhoto == null || (searchParameter.HasPhoto == 0 ? m.Candidate.CandidateImage == null : m.Candidate.CandidateImage != null))
&& (searchParameter.MinBirthDate == null || m.Candidate.BirthDate >= searchParameter.MinBirthDate)
&& (searchParameter.MaxBirthDate == null || m.Candidate.BirthDate <= searchParameter.MaxBirthDate)
&& (string.IsNullOrEmpty(searchParameter.Address) || m.Candidate.CandidateContactInfo.ActualAddress.Contains(searchParameter.Address) || m.Candidate.CandidateContactInfo.RegisteredAddress.Contains(searchParameter.Address))
&& (string.IsNullOrEmpty(searchParameter.Email) || m.Candidate.User.Email.Contains(searchParameter.Email))
&& (regions.Count == 0 || regions.Contains(m.Candidate.CandidateContactInfo.RegionId.Value))
&& (districts.Count == 0 || districts.Contains(m.Candidate.CandidateContactInfo.DistrictId.Value))
&& (educationDegrees.Count == 0 || m.Candidate.CandidateEducations.Any(k => educationDegrees.Contains(k.DegreeId)) || m.Candidate.CandidateCustomEducations.Any(l => educationDegrees.Contains(l.DegreeId)))
&& (searchParameter.EntranceScoreMin == null || m.Candidate.CandidateEducations.Any(k => k.EntrancePoint >= searchParameter.EntranceScoreMin) || m.Candidate.CandidateCustomEducations.Any(l => l.EntrancePoint >= searchParameter.EntranceScoreMin))
&& (searchParameter.EntranceScoreMax == null || m.Candidate.CandidateEducations.Any(k => k.EntrancePoint <= searchParameter.EntranceScoreMax) || m.Candidate.CandidateCustomEducations.Any(l => l.EntrancePoint <= searchParameter.EntranceScoreMax))
&& (string.IsNullOrEmpty(searchParameter.Profession) || m.Candidate.CandidateCustomEducations.Any(p => p.Profession.Contains(searchParameter.Profession)) || m.Candidate.CandidateEducations.Any(p => p.Profession.Contains(searchParameter.Profession)))
&& (string.IsNullOrEmpty(searchParameter.Position) || m.Candidate.CandidateBackgrounds.Any(k => k.Position.Contains(searchParameter.Position)))
&& (string.IsNullOrEmpty(searchParameter.CompanyName) || m.Candidate.CandidateBackgrounds.Any(k => k.CompanyName.Contains(searchParameter.CompanyName)))
&& (computerSkills.Count == 0 || m.Candidate.ComputerSkills.Any(l => computerSkills.Contains(l.SkillId)))
//&& (languageSkills.Count == 0 || m.Candidate.CandidateLanguages.Any(l => languageSkills.Any(c => c.LanguageId == l.LanguageId && c.Writing >= l.Writing && c.Reading >= l.Reading && c.Speaking >= l.Speaking)))
&& (string.IsNullOrEmpty(searchParameter.Phone) || m.Candidate.CandidateMobilPhones.Any(l => l.PhoneNumber.Contains(searchParameter.Phone)))
&& (string.IsNullOrEmpty(searchParameter.CertificateName) || m.Candidate.CandidateCertificates.Any(n => n.Name.Contains(searchParameter.CertificateName)))
&& (string.IsNullOrEmpty(searchParameter.Organization) || m.Candidate.CandidateCertificates.Any(n => n.IssuedBy.Contains(searchParameter.Organization)))
&& (fields.Count == 0 || m.Candidate.CandidateIndustryFields.Any(l => fields.Contains(l.DesiredFieldId)))
&& (skills.Count == 0 || m.Candidate.CandidateSkills.Any(l => skills.Contains(l.SkillId)))
&& (workExperiences.Count == 0 || m.Candidate.CandidateBackgrounds.Any(l => workExperiences.Contains(l.WorkExperienceId ?? -1)))
&& (searchParameter.HasExperience == null || (!searchParameter.HasExperience.Value ? m.Candidate.CandidateBackgrounds.Count == 0 : m.Candidate.CandidateBackgrounds.Count != 0))
&& (searchParameter.GeneralSearch == null || m.Candidate.Name.Contains(searchParameter.GeneralSearch)
|| m.Candidate.Surname.Contains(searchParameter.GeneralSearch)
|| m.Candidate.FatherName.Contains(searchParameter.GeneralSearch)
|| m.Candidate.CandidateContactInfo.ActualAddress.Contains(searchParameter.GeneralSearch)
|| m.Candidate.CandidateContactInfo.RegisteredAddress.Contains(searchParameter.GeneralSearch)
|| m.Candidate.User.Email.Contains(searchParameter.GeneralSearch)
|| m.Candidate.CandidateCustomEducations.Any(p => p.InstitutionName.Contains(searchParameter.GeneralSearch))
|| m.Candidate.CandidateCustomEducations.Any(p => p.Profession.Contains(searchParameter.GeneralSearch))
|| m.Candidate.CandidateEducations.Any(p => p.Profession.Contains(searchParameter.GeneralSearch))
|| m.Candidate.CandidateBackgrounds.Any(p => p.Position.Contains(searchParameter.GeneralSearch))
|| m.Candidate.CandidateBackgrounds.Any(p => p.CompanyName.Contains(searchParameter.GeneralSearch))
|| m.Candidate.CandidateMobilPhones.Any(p => p.PhoneNumber.Contains(searchParameter.GeneralSearch))
|| m.Candidate.CandidateCertificates.Any(p => p.Name.Contains(searchParameter.GeneralSearch))
|| m.Candidate.CandidateCertificates.Any(p => p.IssuedBy.Contains(searchParameter.GeneralSearch))));
您可以做很多事情。你需要循序渐进地工作,并期望有时会做一些让事情变得更糟的事情。数据库查询执行引擎很复杂,它们的行为可能是意外的(例如,我对查询做了一次小小的更改,它的速度快了约 100 倍!)。你还有很多东西要学。
所有这些都可能成为解决方案的一部分。
First:使用EF的日志功能来捕获SQL和参数。然后手动执行以 (1) 查看数据库方面需要多长时间,& (2) 使用
EXPLAIN
查看数据库如何执行查询:花时间了解结果。
您将需要它来识别要添加的索引(并评估对 SQL 的更改)。
第二:如果您不需要使用更改跟踪来允许设置属性然后保存更改:避免
Include
,ThenInclude
:改为使用join
并投影结果(select
子句)。这还应该允许您避免从所有表中获取所有数据:只需获取您需要的数据(可能节省大量内存)。请记住,当您需要集合时,可以将查询表达式放入投影中。
如果这是一个查询页面,这似乎是可能的。
另外,使用
AsNoTracking()
! (参见此处。)
第三:替换
&& (string.IsNullOrEmpty(searchParameter.Name) || m.Candidate.Name.Contains(searchParameter.Name))
与
var query = /* everything before this point */
if (!string.IsNullOrEmpty(searchParameter.Name) {
query = query.Where(x => x.Candidate.Name.Contains(searchParameter.Name));
}
仅构建会改变结果的条件。
这将为数据库节省不需要在数据库上完成的工作,并且允许这些子句sargable。
(对于一组或条件,您还可以使用
Expression
类型手动查看来构建表达式树,因为您不能仅将一个分层到另一个上。)
最后:LINQ to SQL 很棒......直到查询开始真正完成,此时您最好编写自己的 SQL 来提供完全控制(并且还可以访问 LINQ 中未公开的数据库查询功能)。
不要陷入沉没成本谬误:准备好放弃只会导致死胡同的工作。