【问题标题】:Use OracleDataReader with a large amount of values for an IN statement对 IN 语句使用具有大量值的 OracleDataReader
【发布时间】:2019-12-22 03:18:31
【问题描述】:

目前,我正在尝试实现一个数据读取器来执行一个特别大的查询。当前实现使用实体框架,但由于查询的性质,它非常慢(大约 4 分半钟)。

这是当前使用 EF 的实现:

public List<SomeDataModel> GetSomeData(List<string> SomeValues, string setId)
{
    var ret = new List<SomeDataModel>();

    using(var context = new SomeDBContext())
    {
        var data = context.SomeEntity.Where(x => x.SetId == setId && SomeValues.Contains(x.SomeValue));
        data.ForEach(x => ret.Add(mapper.Map<SomeDataModel>(x))); // mapper is an instance of AutoMapper via dependency injection
    }
    return ret; 
}

理想情况下,我想生成一个更基本的查询字符串并通过 OracleDataReader 提取数据。问题是这样的:在 Oracle 中的 IN 语句中,您只能有 1000 个值。 SomeValues 参数可以是 5,000 到 25,000 之间的任何值,所以我想在后端 EF 会自行生成多个查询,但就像我说的那样,它非常慢。

这是我试图采取的方向:

public List<SomeDataModel> GetSomeData(List<string> SomeValues, string setId)
{
    var ret = new List<SomeDataModel>();
    const int MAX_CHUNK_SIZE = 1000;
    var totalPages = (int)Math.Ceiling((decimal)SomeValues.Count / MAX_CHUNK_SIZE);

    for(var i = 0; i < totalPages; i++)        
    {
            var chunkItems = SomeValues.Skip(i * MAX_CHUNK_SIZE).Take(MAX_CHUNK_SIZE).ToList();
            pageList.Add(chunkItems);
    }

        using (var context = new CASTDbContext())
        {
            var connStr = context.Database.Connection.ConnectionString;
            using (var conn = new OracleConnection(connStr))
            {
                foreach(var page in pageList)
                {
                    var queryStr = string.Format("SELECT * FROM SomeTable WHERE SomeColumn IN ({0})", "(" + string.Join(",", page.ToArray())  + ")");
                    var cmd = new OracleCommand(queryStr, conn);
                    using (var reader = cmd.ExecuteReader())
                    {
                        while(reader.Read())
                        {
                            var newItem = new SomeDataModel();
                            newItem.Something = reader["Something"].ToString();
                            ret.Add(newItem);
                        }
                    }
                }                                      
            }
        }
    return ret; 
}

我认为期望的结果是为读者有效地生成多个查询,或者构建一个可以有效处理这种情况的查询。我在第二个示例中的内容是目前的占位符代码。

【问题讨论】:

  • 你为什么使用OracleDataReader?为什么不使用 EF 运行存储过程?
  • 不幸的是,不使用存储过程是一种肤浅的要求。它引用的表非常不稳定,并且引用它的其他存储过程存在问题,所以我被告知不要使用它。
  • 如果您将要查询的项目列表拆分为页面,您是否尝试过使用 EF 执行此操作?这方面的表现如何?我的意思是像在第二个示例中那样构建pageList,然后为每个页面执行Contains 查询?
  • 很遗憾你不能使用 proc,否则你可以传递所有值 as an array 并且查询可能根本不需要时间。
  • @gnud 我也尝试过这条路线,但性能并没有太大差异。

标签: c# oracle entity-framework oracle11g odp.net


【解决方案1】:

一开始,

如果我们使用列对,Oracle 可以在IN 列表中获取超过 1000 个值。

所以,下面会报错:

SELECT * FROM TABLE 
WHERE COL1 IN (VAL1, VAL2,... VAL1000, VAL1001);

但以下将起作用:

SELECT * FROM TABLE 
WHERE (COL,1) IN ( (VAL1,1), (VAL2,1),... (VAL1000,1), 
(VAL1001,1).....(VAL9999,1) ); 
-- we have used pair of value and 1 to be compared with col and 1

希望,这将为您解决问题提供指导。是的,我不确定性能,所以请在最后检查一下。

干杯!!

【讨论】:

  • 似乎是解决 Oracle 限制的好方法。我只用数据阅读器尝试过,但性能并不完全符合 EF。
【解决方案2】:

可能有帮助的东西:

根据您的 SomeEntity 和 SomeDataModel 的外观,您可以从数据库中加载不需要的值,甚至会触发延迟加载,因为在填充数据模型时,它引用了未急切加载的相关实体。使用 Automapper,我建议将 ProjectTo 方法用于 Linq 查询。这将确保数据命中只发生一次,并且只返回数据模型所需的字段。

var results = context.SomeEntity
    .Where(x => x.SetId == setId && SomeValues.Contains(x.SomeValue))
    .ProjectTo<SomeDataModel>()
    .ToList();

或使用 Automapper 9:

var results = mapper.ProjectTo<SomeDataModel>(context.SomeEntity
    .Where(x => x.SetId == setId && SomeValues.Contains(x.SomeValue)))
    .ToList();

像这样查询大量任意值永远不会有效。您还应该检查数据是否被索引。捕获正在运行以检索数据的 SQL 后,通过合适的分析器(我对 Oracle 工具不太熟悉)将其提供给它,以查看是否有索引建议。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-16
    • 1970-01-01
    • 2019-12-29
    • 2011-03-25
    相关资源
    最近更新 更多