【问题标题】:Oracle: Coercing VARCHAR2 and CLOB to the same type without truncationOracle:将 VARCHAR2 和 CLOB 强制为相同类型而不截断
【发布时间】:2013-12-08 10:15:22
【问题描述】:

在支持 MS SQL Server、MySQL 和 Oracle 的应用中,有一个包含以下相关列的表(此处显示的类型适用于 Oracle):

ShortText VARCHAR2(1700) indexed
LongText CLOB

应用程序在 ShortText 中存储 850 个字符或更少的值,在 LongText 中存储更长的值。我需要创建一个返回该数据的视图,无论它位于哪个列。这适用于 SQL Server 和 MySQL:

SELECT
  CASE
    WHEN ShortText IS NOT NULL THEN ShortText
    ELSE LongText
  END AS TheValue
FROM MyTable

但是,在 Oracle 上,它会生成此错误:

ORA-00932: inconsistent datatypes: expected CHAR got CLOB 

...意味着Oracle不会将两列隐式转换为相同的类型,因此查询必须显式执行。不希望数据被截断,因此所使用的类型必须能够容纳与 CLOB 一样多的数据,据我了解(不是 Oracle 专家)意味着 CLOB,只有,没有其他选择可用。

这适用于 Oracle:

SELECT
  CASE
    WHEN ShortText IS NOT NULL THEN TO_CLOB(ShortText)
    ELSE LongText
  END AS TheValue
FROM MyTable

但是,性能非常糟糕。直接返回 LongText 的查询大约 9k 行需要 70-80 毫秒,但上述构造需要 30 到 60 秒,不可接受。

所以:

  1. 是否有任何其他 Oracle 类型我可以强制这两列 可以容纳与 CLOB 一样多的数据?理想情况下还有更多 面向文本的,例如 MySQL 的 LONGTEXT,或 SQL Server 的 NTEXT(甚至 更好,NVARCHAR(MAX))?
  2. 我应该考虑其他任何方法吗?

一些细节,尤其是@Guido Leenders 要求的细节:

Oracle版本:Oracle Database 11g 11.2.0.1.0 64bit Production
不确定我是否是唯一的用户,但相对时间仍然惊人。

我看到我之前发布的性能的小桌子的统计数据:
  行数:9,237
  varchar 列总长度:148,516
  clob 列总长度:227,020

【问题讨论】:

  • 如果你要将所有的短文本放入长文本中,并且只有一个 CLOB 列,这是否会使事情变得更快?
  • 更容易,但是如上所述,短的被索引了,我很漂亮 CLOB 不能。它也可以连接,并支持 WHERE 子句中的等于测试。这是一些通用存储基础架构的一部分,其中数据的子集用于特定目的,因此当已知相关的特定数据始终适合短列时,这些功能很有价值。
  • 请注意,Oracle 12c 将 varchar2 的最大大小增加到 32,767。
  • @David Aldridge:很高兴知道,将来会有所帮助,但目前,该应用还需要支持旧的 Oracle 版本。

标签: performance oracle casting clob coerce


【解决方案1】:

to_clob 非常昂贵,因此请尽量避免使用它。但我认为它对于 9K 行应该表现得很好。以下基于我们开发的具有相似数据模型行为的应用程序之一的测试用例:

create table bubs_projecten_sample
( id number
, toelichting varchar2(1700)
, toelichting_l clob
)

begin
  for i in 1..10000
  loop
    insert into bubs_projecten_sample
    ( id
    , toelichting
    , toelichting_l
    )
    values
    ( i
    , case when mod(i, 2) = 0 then 'short' else null end
    , case when mod(i, 2) = 0 then rpad('long', i, '*') else null end
    )
    ;
  end loop;
  commit;
end;

现在确保缓存中的所有内容和脏块都已写出:

select *
from   bubs_projecten_sample

测试性能:

create table bubs_projecten_flat
as
select id
,      to_clob(toelichting) toelichting_any
from   bubs_projecten_sample
where  toelichting is not null
union all
select id
,      toelichting_l
from   bubs_projecten_sample
where  toelichting_l is not null

在普通入门级服务器上创建表耗时不到 1 秒,包括写出数据、17K 一致获取、4K 物理读取。存储在磁盘(注意 rpad)上的 toelichting 为 25K,toelichting_l 为 16M。

您能否进一步详细说明问题?

请检查大型 CLOB 是否未内联存储。通常,大型 CLOB 存储在单独的系统维护表中。将大型 CLOB 存储在表中会使使用全表扫描遍历表变得昂贵。

另外,我可以想象总是填充两列。您仍然可以为前这么多字符建立索引。您只需要使用指示符记住表中是 CLOB 还是 shortText 列是前导的。

作为旁注;我发现 850 和 1700 之间存在差异。我建议使它们相等,但请记住检查您是否使用字符语义创建表。这可以通过使用“varchar2(850 char)”在语句级别完成。请注意,Oracle 实际上会创建一个适合 850 * 4 字节的列(至少在 AL32UTF8 中,“32”代表“每个字符最多 4 个字节”)。祝你好运!

【讨论】:

  • 嗯,考虑到我上面的查询返回原始 LongText 值与 TO_CLOB(LongText) 的相对时间,不要认为内联 CLOB 存储有什么不同。我想知道为什么你的例子比我看到的要快得多。我正在使用的开发服务器不是超级强大,但它也不是无助的,并且在其他方​​面的性能(例如查询的原始 LongText 版本)如果不是很好的话也可以。重新列长度,感谢您的反馈。不要懦弱,但我会将其转发给做出这些决定的开发人员,我自己对 Oracle 的支持并不强。
  • 不是很重要,但事实证明,列大小不匹配是我获取该信息的方式的产物,而不是列本身。再次感谢您指出这一点。
  • 您进行性能测试的确切 Oracle 版本是什么?您是基准测试时的唯一用户吗?您能否在问题中包含 DDL 以重新创建表和确切的统计信息(行数、sum(length(clob))、sum(length(text)) 字段?)性能差异是确实很奇怪。
  • 将您要求的细节添加到原始问题中,部分原因是它们可以是多行的。此处没有可用的原始 DDL,但两个相关列的规范如所述。感谢您的参与!
  • 嗯,考虑到您在问题中提出的统计数据,无论 clob 是内联存储还是外部存储都无关紧要,它都非常小,每条记录只有几个字节。在 11.2.0.1 上,我不知道 clob 问题,只是回忆一下描述元数据以纠正数字列上的数据类型(精度/比例丢失)的问题。访问 SGA 应该不成问题,并且资源充足。受别人影响真的太少了。您可以尝试重写为 UNION ALL,然后说明性能数据吗?这是非常小的数据,然后相同的场景需要更长的时间。
猜你喜欢
  • 2012-11-04
  • 1970-01-01
  • 1970-01-01
  • 2011-02-11
  • 1970-01-01
  • 2023-01-05
  • 1970-01-01
  • 2012-10-03
  • 2015-10-16
相关资源
最近更新 更多