有效地检查文本列中是不是存在文本
Posted
技术标签:
【中文标题】有效地检查文本列中是不是存在文本【英文标题】:Checking for the presence of text in a text column efficiently有效地检查文本列中是否存在文本 【发布时间】:2009-01-29 12:05:23 【问题描述】:我有一个大约有 2,000,000 行的表。我需要查询其中一列以检索字符串作为值的一部分存在的行。
当我运行查询时,我会知道字符串的位置,但不是事先知道的。因此,采用子字符串的视图不是一种选择。
据我所知,我有三个选择
-
使用类似“% %”
使用指令
使用 substr
如果我对 dba 很好,我确实可以选择创建基于函数的索引。
目前所有查询都需要大约两秒钟。有没有人知道这些选项中的哪一个效果最好,或者是否还有其他选项?该选择将用于每隔几秒删除一次,它通常会选择 10 行。
编辑更多信息
问题出现在我们使用表来存储具有任意键和值的对象时。对象来自我们的系统外部,因此我们控制它们的范围有限,因此文本列类似于 'key1=abc,key2=def,keyn=ghi' 我知道这是非常非规范化的,但因为我们不知道是什么键将(在某种程度上)它是存储和检索值的可靠方法。检索一行是相当快的,因为我们正在搜索整个列,这是索引。但是如果我们要检索 key2=def 的行,性能并不好。
我们也许可以创建一个包含最常用键列的表,但我想知道是否有办法通过现有设置提高性能。
【问题讨论】:
请发布一些示例数据和想要的查询结果 你不能在它自己的列中拥有你需要的数据吗?列内容应该是“原子的” 【参考方案1】:在甲骨文 10 中:
CREATE TABLE test (tst_test VARCHAR2(200));
CREATE INDEX ix_re_1 ON test(REGEXP_REPLACE(REGEXP_SUBSTR(tst_test, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1'))
SELECT *
FROM TEST
WHERE REGEXP_REPLACE(REGEXP_SUBSTR(TST_TEST, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1') = 'TEST'
这将使用新选择的索引。
您将需要与数据中 KEY
s 一样多的索引。
INDEX
的存在当然会影响性能,但这几乎不依赖于REGEXP
的存在:
SQL> CREATE INDEX ix_test ON test (tst_test)
2 /
Index created
Executed in 0,016 seconds
SQL> INSERT
2 INTO test (tst_test)
3 SELECT 'KEY1=' || level || ';KEY2=' || (level + 10000)
4 FROM dual
5 CONNECT BY
6 LEVEL <= 1000000
7 /
1000000 rows inserted
Executed in 47,781 seconds
SQL> TRUNCATE TABLE test
2 /
Table truncated
Executed in 2,546 seconds
SQL> DROP INDEX ix_test
2 /
Index dropped
Executed in 0 seconds
SQL> CREATE INDEX ix_re_1 ON test(REGEXP_REPLACE(REGEXP_SUBSTR(tst_test, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1'))
2 /
Index created
Executed in 0,015 seconds
SQL> INSERT
2 INTO test (tst_test)
3 SELECT 'KEY1=' || level || ';KEY2=' || (level + 10000)
4 FROM dual
5 CONNECT BY
6 LEVEL <= 1000000
7 /
1000000 rows inserted
Executed in 53,375 seconds
如您所见,在我的速度不是很快的机器(Core2 4300
、1 Gb RAM
)上,您可以每秒将20000
记录插入索引字段,并且此速率几乎不取决于INDEX
的类型正在使用:普通或基于函数。
【讨论】:
这可能适用于选择,但我们的 dba 指出它会使插入速度慢得多,因为它必须构建索引。非常感谢您的回答。【参考方案2】:您可以使用Tom Kyte's runstats package 来比较不同实现的性能 - 每个都在循环中运行 1000 次。例如,我刚刚将 LIKE 与 SUBSTR 进行了比较,它说 LIKE 更快,大约需要 SUBSTR 的 80% 的时间。
请注意,“col LIKE '%xxx%'”不同于“SUBSTR(col,5,3) = 'xxx'”。等效的 LIKE 将是:
col LIKE '____xxx%'
对要忽略的每个前导字符使用一个“_”。
我认为无论您采用哪种方式,结果都会相似——它总是涉及全表(或者可能是全索引)扫描。只有在创建索引时知道子字符串的偏移量时,基于函数的索引才会有所帮助。
当您说“选择将用于每隔几秒删除一次”时,我很担心。这确实暗示了某处的设计缺陷,但在不知道要求的情况下很难说。
更新:
如果您的列值类似于 'key1=abc,key2=def,keyn=ghi' 那么也许您可以考虑添加另一个像这样的表:
create table key_values
( main_table_id references main_table
, key_value varchar2(50)
, primary key (fk_col, key_value)
);
create index key_values_idx on key_values (key_value);
拆分键值并将它们存储在此表中,如下所示:
main_table_id key_value
123 key1=abc
123 key2=def
123 key3=ghi
(例如,这可以在 main_table 上的 AFTER INSERT 触发器中完成)
那么你的删除可能是:
delete main_table
where id in (select main_table_id from key_values
where key_value = 'key2=def');
【讨论】:
【参考方案3】:你能提供更多信息吗?
您是在查询字符串列的任意子字符串,还是在列中存储的字符串上有一些语法可以允许进行一些预处理以最大程度地减少重复工作?
您是否已经对您的三个选项进行了任何计时测试,以确定它们在您查询的数据上的相对性能?
【讨论】:
【参考方案4】:我建议重新考虑你的逻辑。
与其寻找字符串存在的位置,不如检查它的长度是否 >0 并且不是字符串可能会更快。
您可以使用oracle中的TRANSLATE函数将所有非字符串字符转换为null,然后检查结果是否为null。
【讨论】:
“只有数字”从何而来?我在问题中没有看到任何暗示。【参考方案5】:对表格设计发表评论的单独答案。
难道你不能至少有一个 KEY/VALUE 结构,所以与其存储在单个列中,'key1=abc,key2=def,keyn=ghi' 你应该有一个类似的子表
KEY VALUE
key1 abc
key2 def
key3 ghi
然后您可以在键和值上创建单个索引,并且您的查询要简单得多(因为我认为您实际上是在寻找给定键值的完全匹配)。
有些人可能会评论说这是一个糟糕的设计,但我认为它比你现在的更好。
【讨论】:
我们可能会更改表格,使其包含最常用键的列。但是,我们使用它来存储来自另一个系统的值,因此没有确定的键列表。我们正在研究为此使用材质化视图 我的建议仍然允许存储任意键名。 “key1”、“key2”等将是列中的值,而不是列本身。【参考方案6】:如果您总是要查找相同的子字符串,那么使用 INSTR 和基于函数的索引对我来说很有意义。如果您有一小组要查找的常量子字符串,您也可以这样做,为每个子字符串创建一个 FBI。
Quassnoi 的 REGEXP 想法看起来也很有前景。我还没有在 Oracle 中使用过正则表达式。
我认为 Oracle Text 将是另一种方式。相关信息here
【讨论】:
【参考方案7】:不确定是否要改进现有设置,但 Lucene(全文搜索库;已移植到许多平台)确实可以提供帮助。将索引与数据库同步会带来额外的负担,但如果您有任何类似于某种编程语言中的服务层的东西,这将变得很容易。
【讨论】:
【参考方案8】:与 Anton Gogolev 的回应类似,Oracle 确实包含了一个文本搜索引擎,记录在 here
还有可扩展的索引,因此您可以构建自己的索引结构,记录在 here
正如您所同意的,这是一个非常糟糕的数据结构,我认为您将难以实现每隔几秒钟删除一次内容的目标。根据这些数据的输入方式,我会考虑在加载时正确构建数据,至少在“parent_id”、“key_name”、“key_value”行的范围内。
【讨论】:
以上是关于有效地检查文本列中是不是存在文本的主要内容,如果未能解决你的问题,请参考以下文章