【问题标题】:Performance issues in SQL query with a hierarchical relationship具有层次关系的 SQL 查询中的性能问题
【发布时间】:2015-12-21 20:47:07
【问题描述】:

我有一个表示父子关系的 Oracle 表,我想提高在层次结构中搜索祖先记录的查询的性能。我在这里用小数据集进行测试,虽然实际表要大得多:

id   name    parent_id   tagged
==   ====    =========   ======
1    One     null        null  
2    Two     1           1
3    Three   2           null
4    Four    3           null
5    Five    null        null
6    Six     5           1
7    Seven   6           null
8    Eight   null        null
9    Nine    8           null

parent_id 以外键关系引用同一表中的 id。

我想编写一个查询,返回每个叶子记录(那些没有后代的记录......在这个例子中是 id 4 和 id 7),它的祖先记录有 tagged = 1(回溯到 parent_id 关系)。

所以,对于上述源数据,我希望我的查询返回:

id   name    tagged_ancestor_id
==   ====    ==================  
4    Four    2
7    Seven   6

我当前检索这些记录的查询是:

select * from (
    select id, 
           name,
           connect_by_root id tagged_ancestor_id    
    from mytree 
    connect by prior id = parent_id
    start with tagged is not null
) m1            
where not exists (
    select * from mytree m2 where m2.parent_id = m1.id
)

这个查询在这个简单的小示例表上运行良好,但在我的真实表上它的性能很糟糕,它有大约 11,000,000 条记录。查询需要一分钟多的时间才能运行。

  • connect by 子句中的两个字段都有索引。
  • start with 子句中的“tagged”字段也有索引,我的表中大约有 1,500,000 条记录,该字段中的值为非空值。
  • where 子句似乎不是问题,因为当修改它以使用where name = 'somename' 而不是where not exists ... 返回特定名称(也被索引)时,查询仍然需要大约相同的时间.

那么,我可以使用哪些策略来尝试使此层次结构上的这些类型查询运行得更快?

【问题讨论】:

  • 如果您可以修改您的架构,您可以重建它以存储修改后的前序树遍历,但您需要添加 2 个额外的列(左值和右值)。几年前,我按照我目前找不到的教程在 mysql 中成功地做到了这一点。 :( 你应该以空间为代价获得一些额外的性能。
  • 我确实可以控制架构,因此如果这是提高此性能的最佳方法,那么添加一些列就可以了。我想我可以将一个标记的祖先存储在它自己的字段中并成为完成了,但似乎这个查询可以更快。
  • 这里是适当的维基百科参考:en.wikipedia.org/wiki/Nested_set_model 我不确定我是否理解将一个标记的祖先存储在它自己的领域会如何帮助你(除非你存储所有它们?)但我是可能不明白你的问题...?

标签: sql oracle hierarchy connect-by


【解决方案1】:

这是我首先要检查的内容:

  1. 确保您的表具有主键。
  2. 确保表上的统计信息是最新的。使用 DBMS_STATS.GATHER_TABLE_STATS 收集统计信息。请参阅此 URL:(对于 ORACLE 版本 11.1):

    @987654321@

  3. 即使您分别在两个字段上都有索引,您仍然需要

    两个字段组合的索引;在 ID 和 PARENT_ID 上创建索引:

    在 TABLE_NAME(ID, PARENT_ID) 上创建索引;

    查看此网址:

    @987654322@

  4. 确保基础表没有行链接或其他问题(例如损坏)。
  5. 确保表和所有索引都在同一个表空间中。

【讨论】:

  • 感谢您的建议。我会检查这些。
【解决方案2】:

如果没有要测试的数据量,我不确定这是否会更快......但需要考虑一些事情。我想我希望只从那些被标记的那些开始,并且只有那些叶子是我们正在处理的较小的处理量,这可能会导致性能提升。但字符串操作的开销似乎很骇人。

  with cte(id, name, parent_id, tagged) as (
SELECT 1, 'ONE', null, null from dual union all
SELECT 2, 'TWO', 1, 1 from dual union all
SELECT 3, 'THREE', 2, null from dual union all
SELECT 4, 'FOUR', 3, null from dual union all
select 5, 'FIVE', null, null from dual union all
select 6, 'SIX', 5, 1 from dual union all
select 7, 'SEVEN', 6, null from dual union all
select 8, 'EIGHT', null, null from dual union all
select 9, 'NINE', 8, null from dual),

Leafs(id, name) as (select id, Name
           from cte
where connect_by_isleaf = 1
Start with parent_Id is null
connect by nocycle prior id =parent_id),

Tagged as (SELECT id, name, SYS_CONNECT_BY_PATH(ID, '/') Path, substr(SYS_CONNECT_BY_PATH(ID, '/'),2,instr(SYS_CONNECT_BY_PATH(ID, '/'),'/',2)-2) as Leaf
from cte
where tagged=1
start with id in (select id from leafs)
connect by nocycle prior parent_id = id)

select l.*, T.ID as Tagged_ancestor from leafs L
inner join tagged t
 on l.id = t.leaf

本质上,我创建了 3 个 cte,一个用于数据 (Cte),一个用于叶子 (leafs),一个用于标记记录 (tagged)

我们遍历层次结构两次。一次得到所有的叶子,一次得到所有的标记。然后,我们从标记的层次结构中解析出第一个叶值并将其连接回叶,以获取与标记记录相关的叶。

至于这是否比你正在做的更快...... 耸耸肩我不想花时间测试,因为我没有你的索引也没有你的数据音量

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-05
    • 2013-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-13
    相关资源
    最近更新 更多