【问题标题】:Best way to save a static list using SQL Server, EF6使用 SQL Server、EF6 保存静态列表的最佳方法
【发布时间】:2014-11-26 21:12:34
【问题描述】:

我正在开始重写现有应用程序。当前的性能瓶颈之一是将用户生成的员工列表保存到静态列表中,以便他们以后可以返回并查看相同的员工列表。

下面是我正在寻找的功能的一个简单示例。生成的列表将由比示例中更复杂的查询生成。

场景: 用户搜索所有上夜班的员工,并希望保存此列表以供以后加载。他们希望列表始终返回结果,因为这是他们第一次运行搜索。 IE。如果一个新员工被添加到夜班,他们不应该出现在名单上。


我尝试过的:

目前,将结果列表中的所有 ID 作为字符串数组存储,然后使用这些 ID 重新构建查询的解决方案非常糟糕。这是非常低效的,并且会导致具有太多参数的大型列表出现问题。

我也尝试过从保存的 ID 数组构建一个表,然后加入,但是,这在大型列表(20,000 多名员工)上非常慢,并且经常导致超时。

我目前的想法是为每个列表创建一个新表,然后在该表和 Employee 表上调用 JOIN。但是,如果 100 个用户每人保存 10 个大型列表(20,000 多名员工),它很快就会成为存储和表管理的噩梦。

我认为这是一个相当普遍的问题的解决方案。但是,我找不到任何关于如何存储静态列表的示例或最佳实践(我可能正在寻找错误的东西)。有人对如何最好地处理此类用例场景有任何一般概念吗?

更新: 我想我之前尝试过以下设置,但由于某种原因无法正常工作,这是几年前的事了;但回首往事,似乎最有意义。我认为我遇到的问题是 NHibernate 在 Linq 中存在子选择问题。但是,这不再是限制。

我想我有一个StaticSavedLists 的表和一个索引表,该表将人员​​(上一个示例中的员工)链接到多对多映射中的员工列表。 c# 中的类如下所示:

public class StaticSavedList : BaseModel
{
    public string Name { get; set; }
    public IList<StaticSavedListPersonIdx> PersonsIdx { get; set; } //Has many persons
}

public class StaticSavedListPersonIdx : BaseModel
{
    public StaticSavedList StaticSavedList { get; set; }
    public Person Person { get; set; }
}

【问题讨论】:

    标签: c# sql entity-framework tsql search


    【解决方案1】:

    您可能需要 1 个包含搜索“标题”详细信息的表,其中将包含搜索的 ID,然后是包含每个搜索条目的第二个表。然后您只需要以某种方式获取 ID(可能通过用户 ID 和轮班日期)并使用它来加入结果和员工表。

    这是架构的外观

    ShiftSearches

    SearchID    int (PK)
    ShiftDate   datetime
    

    搜索结果

    SearchID    int (PK)
    EmployeeID  int (PK)
    

    员工

    EmployeeID  int (PK)
    FirstName   varchar
    etc ...
    

    可能的 LINQ 查询

    DateTime shiftDate = new DateTime(2014,11,26);
    int searchId = db.ShiftSearches.Single(s => s.ShiftDate == shiftDate).SearchID;
    
    var results = from r in db.SearchResults where r.SearchID == searchId
        join e in Employees on r.EmployeeID equals e.EmployeeID
        select e;
    

    这样你只需要一张表,而且它很薄,所以不应该占用太多空间 - 因为你只是存储了所需的搜索和员工 ID,所以无论如何你不能真正让数据变小。

    您发布的类结构非常符合这个概念。

    【讨论】:

      【解决方案2】:

      实体框架可能并非在所有情况下都是合适的技术选择,一次处理 20k 行的批次可能是其中一种情况。

      但是,我相信您的数据模型设计是好的。下面,在 Sql Only 上,可以显示包含 (ListId, EmployeeId) 对的 60k 行可以在一秒钟内插入到一个合理聚集的 ListSearchEmployee 表中,随后,可以将 20k 行列表之一连接回冷启动后 1.8 秒内完整的Employee 行。

      性能瓶颈更有可能是原始用户搜索 - 大概这可能是对您的员工 + 相关表执行的几乎任意查询,这将难以为所有排列编制索引。

      一些性能建议(用于列表保存+重新获取):

      • 使用 SqlBulkCopy 将 EmployeeId 批量转储到列表表中
      • 使用一些低级别的东西,比如基本的SqlReader 来为 UI 取回列表数据(虽然我猜它会被分页? - 如果是这样,关闭AsNoTracking()DbSet.SqlQuery 可能就足够了。
      • 不要在 List 表上使用外键 - 这会减慢插入速度。
      • 不要尝试同步清理不需要的旧列表搜索 - 将它们排队等待删除,并有一个后台进程来执行删除。
      • 因此,ListSearchEmployee 表由于大量流失而经常需要重新索引。
      -- Sample data setup - not considered in the timing
      CREATE TABLE Employee
      (
        EmployeeID INT identity(1,1),
        Name NVARCHAR(100),
        SomeOtherFieldToLessenTheDensityOfEmployee CHAR(500),
      
        PRIMARY KEY(EmployeeID)
      );
      
      CREATE TABLE ListSearch
      (
        ListSearchID INT IDENTITY(1,1) PRIMARY KEY
        -- Other fields you may want to identify the search, e.g. date, which user, which filters etc
      )
      
      CREATE TABLE ListSearchEmployee
      (
        ListSearchID INT,
        EmployeeID INT, -- Don't bother Foreign Keying for performance
      
        PRIMARY KEY CLUSTERED (ListSearchID, EmployeeID)
      );
      
      -- Insert 1M Employees
      WITH cteData AS
      (
         SELECT top 1000000 sc1.name, ROW_NUMBER() OVER (ORDER BY sc1.object_id) AS rn
         FROM sys.columns sc1 CROSS JOIN sys.columns sc2 CROSS JOIN sys.columns sc3
      )
      INSERT INTO Employee(Name)
      SELECT name + CAST(rn AS VARCHAR)
      FROM cteData;
      
      -- Timing : 0.972 seconds on SQLExpress 2012 on an i3
      -- Inserting 3 x 20 k lists of pseudo random employees (but not contigious on the EmployeeId Cluster)
      WITH cteData AS
      (
         SELECT top 20000 1 as listid, ROW_NUMBER()  OVER (ORDER BY sc1.object_id) * 50 AS empid
         FROM sys.columns sc1 CROSS JOIN sys.columns sc2
      
        UNION ALL
      
         SELECT top 20000 2 as listid, ROW_NUMBER()  OVER (ORDER BY sc1.object_id) * 30 AS empid
         FROM sys.columns sc1 CROSS JOIN sys.columns sc2
      
        UNION ALL
      
         SELECT top 20000 3 as listid, ROW_NUMBER()  OVER (ORDER BY sc1.object_id) * 41 AS empid
         FROM sys.columns sc1 CROSS JOIN sys.columns sc2
      
      )
      INSERT INTO ListSearchEmployee(ListSearchID, EmployeeID)
      SELECT listid, empid
      FROM cteData;
      
      DBCC DROPCLEANBUFFERS;
      
      -- Timing : 1.751 seconds on SQLExpress 2012 on an i3
      -- Joining 20k rows
      SELECT * 
      FROM ListSearchEmployee el INNER JOIN Employee e on el.EmployeeID = e.EmployeeID
      WHERE el.ListSearchID = 2
      

      【讨论】:

      • 哇。我非常感谢您花在这个答案上的时间。没想到会有这么详细的回复。我会告诉你进展如何。
      • 您会惊讶于底层 RDBMS 的本机性能 - 在插入时,如果确实需要,您还可以选择拆分 id 插入并在多个连接中并行插入:)
      猜你喜欢
      • 1970-01-01
      • 2015-08-03
      • 1970-01-01
      • 1970-01-01
      • 2017-06-10
      • 2010-09-13
      • 2010-10-16
      • 2010-12-01
      • 1970-01-01
      相关资源
      最近更新 更多