【问题标题】:Optimize Oracle SQL with large 'IN' clause使用大型“IN”子句优化 Oracle SQL
【发布时间】:2023-03-09 00:40:02
【问题描述】:

这里我有一个如下查询:

SELECT field
FROM table
WHERE value IN ('val1', 'val2', 'val3', ... 'valn')

假设 IN 子句中有 2000 个值,该值在其他表中不存在。你有什么想法可以加快这个操作吗?

这个问题可以接受任何一种方法..

谢谢!

【问题讨论】:

  • FWIW,如果您在此类列表中放置超过 1000 个值,Oracle(无论如何,11g)会引发错误。除此之外,它运行良好。

标签: java sql oracle optimization query-optimization


【解决方案1】:
  1. 创建一个涵盖“字段”和“值”的索引。

  2. 将这些 IN 值放在一个临时表中并加入它。

【讨论】:

  • 同意 1。你有关于 2 的性能统计的任何信息或链接 - 似乎有点不直观?关于 IN 的倾斜点的任何指导与插入到 temp 然后 JOIN 到 temp 相比变得效率低下?谢谢
  • 这就是我在采访中的回答。面试官然后评论这个答案之后一定有事可做。我猜他说的是并发访问表和家务。
【解决方案2】:

如果您已经在 value 字段上建立了索引,并且这些值在任何表中都不能用于连接或子选择,那么我认为没有任何优化的可能性。在您的值实际上是“val1”,“val”,...的特殊情况下,您可以使用类似查询,该查询将使用索引来搜索前缀。但我认为这只是一个例子。

【讨论】:

    【解决方案3】:
    SELECT field
    FROM table
    WHERE value IN SELECT somevalue from sometable
    

    据我所知,您将面临另一个问题。这将是“IN”子句的限制。使用它,您可以避免这种情况并有望加快您的查询

    【讨论】:

      【解决方案4】:

      Oracle 查询解析和缓存机制在您使用绑定变量时效果更好。如果使用它们您的查询可能会执行得更好

      选择字段 从表 WHERE value IN (?,? ....) 然后根据需要分配值。

      最好使用企业管理控制台等工具分析实际执行的性能并决定改进。创建索引可能是第一步。

      将潜在值存储在另一个表中并使用 J Horstmann 的建议似乎是一个正确的想法。请试一试。

      【讨论】:

        【解决方案5】:

        选择更好的解决方案需要更多信息。

        1. 查询是否经常执行?
        2. 值 val1、val2 是否固定?
        3. 桌子有多大?

        如果查询频繁执行,值 val1、val2 等是固定的并且表很大(例如 20,000 或更多行)然后将所有值存储在另一个表(例如临时表)中并在值字段上连接两个表。

        如果下面查询中的表很大,应该对 value 字段进行索引以提高性能。

        选择字段 从表 WHERE value IN ('val1', 'val2', 'val3', ... 'valn')

        应该分析这两个表。

        性能更好的原因是优化器会根据表的特性选择最佳的连接方法。如果上面查询中的表非常大,则连接将是嵌套循环连接,并且上面的表应该在列 val 上有一个索引。

        如果上述查询中的表非常小(比如少于 200-300 行),则新表(临时表)应该在 val 列上有索引。

        如果两个表的大小几乎相同,索引将无济于事。

        结论:最佳方案视具体情况而定。

        【讨论】:

          【解决方案6】:

          如果其他建议都不起作用,并且查询需要很长时间,您可以尝试并行运行。

            select /*+ parallel(table) */ field ...
          

          【讨论】:

            【解决方案7】:

            您可以将普通表与填充了值列表的内存表连接起来。

            我不知道如何用 Java 做到这一点,但我知道如何用 C# 做到这一点。我认为Java应该可以实现类似的东西。

            在这里阅读:http://forums.oracle.com/forums/thread.jspa?threadID=892457&tstart=375

            让我们使用一组用户定义类型 (UDT)。 首先创建一个有 100 万行的表:

            create table employees (id number(10) not null primary key, name varchar2(100) );
            
            insert into employees 
            select level l, 'MyName'||to_char(level) 
            from dual connect by level <= 1e6;
            
            1000000 rows created
            
            commit;
            
            exec dbms_stats.gather_schema_stats(USER, cascade=>TRUE);
            

            不,我们转向 C# 代码:

            让我们选择 id 为 3 和 4 的员工。

            使用集合类型 MDSYS.SDO_ELEM_INFO_ARRAY 是因为如果我们使用这个已经预定义的 Oracle 类型,我们就不必定义我们自己的 Oracle 类型。您可以使用最多 1048576 个数字填充集合 MDSYS.SDO_ELEM_INFO_ARRAY。

            using Oracle.DataAccess.Client;
            using Oracle.DataAccess.Types;
            
                [OracleCustomTypeMappingAttribute("MDSYS.SDO_ELEM_INFO_ARRAY")]
                public class NumberArrayFactory : IOracleArrayTypeFactory
                {
                  public Array CreateArray(int numElems)
                  {
                    return new Decimal[numElems];
                  }
            
                  public Array CreateStatusArray(int numElems)
                  {
                    return null;
                  }
                }
            
            
                private void Test()
                {
                  OracleConnectionStringBuilder b = new OracleConnectionStringBuilder();
                  b.UserID = "sna";
                  b.Password = "sna";
                  b.DataSource = "ora11";
                  using (OracleConnection conn = new OracleConnection(b.ToString()))
                  {
                    conn.Open();
                    using (OracleCommand comm = conn.CreateCommand())
                    {
                      comm.CommandText =
                          @" select  /*+ cardinality(tab 10) */ *  " +
                          @" from employees, table(:1) tab " +
                          @" where employees.id = tab.column_value";
            
                      OracleParameter p = new OracleParameter();
                      p.OracleDbType = OracleDbType.Array;
                      p.Direction = ParameterDirection.Input;
                      p.UdtTypeName = "MDSYS.SDO_ELEM_INFO_ARRAY";
                      p.Value = new Decimal[] { 3, 4 };
            
                      comm.Parameters.Add(p);
            
                      int numPersons = 0;
                      using (OracleDataReader reader = comm.ExecuteReader())
                      {
                        while (reader.Read())
                        {
                          MessageBox.Show("Name " + reader[1].ToString());
                          numPersons++;
                        }
                      }
                      conn.Close();
                    }
                  }
                }
            

            employees.id 上的索引在省略提示 /*+ cardinality(tab 10) */ 时不使用。这个索引是Oracle创建的,因为id是主键列。

            这意味着您不必填写临时表。值列表保留在 ram 中,您将表 employees 与内存 table(:1) 选项卡 中的值列表连接起来。

            (wateenmooiedag=TTT)

            【讨论】:

              【解决方案8】:

              这看起来像 Java 中的正确方法:http://knol.google.com/k/oracle-passing-a-list-as-bind-variable#

              它类似于 C# 解决方案。您的值列表保留在内存中(没有临时表),它不会被持久化到磁盘,并且您使用参数化查询,因此查询执行器不必重新解析每个查询。我没有用java尝试过,但我认为它会很快。

              【讨论】:

              • 上面的链接失效了。
              【解决方案9】:

              只需将您改写为存在即可。它会更快。

              【讨论】:

              • 也许你可以举个例子?
              【解决方案10】:

              使用以下方法进行类似查询时,我获得了可接受的性能(执行时间接近于无条件获取行)。

              static final int MAX_QUERY_SET = 1000;
              

              我迭代这些值,并对每个 MAX_QUERY_SET 值进行单独的查询。所以对于 10K 值,我有 10 个查询。我按顺序处理查询。

              在实现了这个算法之后,我就可以使用常量了。对于 30 或 3000 的值,我得到了 3 倍的执行时间。所以我坚持1000。

              如果您无法处理多个查询,这可能不起作用。我的经验是在不同的数据库上收集的(Pervasive,每个语句的字符数限制为 65K),但我认为这个问题很笼统,它的结论应该很普遍。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2020-07-29
                • 2011-10-13
                • 1970-01-01
                • 1970-01-01
                • 2016-04-25
                • 2013-10-10
                • 2020-05-01
                相关资源
                最近更新 更多