4

我有一个存储唯一文本字符串的表,然后通过执行 select 检查该字符串是否存在于数据库中

String checkIfAlreadyScanned = "SELECT id FROM \"STRINGS_DB\"  where STR ='" + mystring + "'";

然后我检查值是否存在。我的数据库有大约 500 万条记录;我可以改进我的方法吗?

例如,也许有一种方法可以创建一个新属性(hashedSTR)并将字符串转换为一些唯一的数字值,然后获取这些数字,而不是字符串?这会更快吗?(这会起作用吗?)

4

9 回答 9

4

为确保最快的处理速度,请确保:

  • 您正在搜索的字段已编入索引(您告诉过一个“唯一”字符串,所以我想已经是这种情况了。因此,“限制 1”不是必需的。否则,应该添加它)
  • 您正在使用ExecuteScalar()Command 对象的方法
于 2012-07-12T19:15:59.997 回答
2

测试没有意义,只需在 where 子句中包含“测试”即可:

INSERT INTO silly_table(the_text)
 'literal_text'
WHERE NOT EXISTS (
    SELECT *
    FROM silly_table
    WHERE the_text = 'literal_text'
    );

现在,您将仅在需要时进行测试:在语句的末尾,该行将存在。没有尝试这样的东西。

对于那些不了解测试没有意义的人:如果测试后的情况不允许在测试后改变,那么测试是有意义的那将需要一个测试和锁定方案。或者,更糟糕的是:交易中的测试。

更新:有效的版本(基本相同):

DROP TABLE exitsnot CASCADE;
CREATE TABLE exitsnot
        ( id SERIAL NOT NULL PRIMARY KEY
        , val INTEGER -- REFERENCES something
        , str varchar -- REFERENCES something
        );

INSERT INTO exitsnot (val)
SELECT 42
WHERE NOT EXISTS (
        SELECT * FROM exitsnot
        WHERE val = 42
        );
INSERT INTO exitsnot (str)
SELECT 'silly text'
WHERE NOT EXISTS (
        SELECT * FROM exitsnot
        WHERE str = 'silly text'
        );
SELECT version();

输出:

DROP TABLE
NOTICE:  CREATE TABLE will create implicit sequence "exitsnot_id_seq" for serial column "exitsnot.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "exitsnot_pkey" for table "exitsnot"
CREATE TABLE
INSERT 0 1
INSERT 0 1
                                           version                                            
----------------------------------------------------------------------------------------------
 PostgreSQL 9.1.2 on i686-pc-linux-gnu, compiled by gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32-bit
(1 row)
于 2012-07-12T23:07:15.977 回答
1
String checkIfAlreadyScanned = "SELECT 1 FROM \"STRINGS_DB\"  where STR ='" + mystring + "'";

如果您的结果集包含一行,那么您有一条记录

于 2012-07-12T19:09:23.540 回答
1

将结果集限制为 1:

String checkIfAlreadyScanned = @"
    SELECT id 
    FROM ""STRINGS_DB""  
    where STR ='" + mystring + @"'
    limit 1";

这,该列上的索引以及@Laurent 的建议ExecuteScalar()将产生最佳结果。

此外,如果mystring有任何机会被用户触摸,则对查询进行参数化以避免 sql 注入。

更清洁的版本:

String checkIfAlreadyScanned = @"
    SELECT id 
    FROM ""STRINGS_DB""  
    where STR = '@mystring'
    limit 1
    ".replace("@mystring", mystring);
于 2012-07-12T19:12:03.993 回答
1

这些文本字符串有多长?如果它们很长,您可以通过存储字符串的散列(连同原始字符串)来提高性能。

CREATE TABLE strings_db (
    id       PRIMARY KEY INT,
    text     TEXT,
    hash     TEXT
);

您的哈希列可以存储 MD5 和、CRC32 或您选择的任何其他哈希算法。它应该被索引。

然后将您的查询修改为:

SELECT id FROM strings_db WHERE hash=calculate_hash(?)

如果您的文本字段的平均大小比散列的大小足够大,那么在较短的字段上进行搜索将有助于磁盘 I/O。这也意味着在插入和选择、计算散列时需要额外的 CPU 开销,以及用于存储散列的额外磁盘空间。因此,所有这些因素都必须考虑在内。

PS 始终使用准备好的语句来避免 SQL 注入攻击!

于 2012-07-12T19:14:23.650 回答
1

实际上,正是您所要求的。但它有一些限制。PostgreSQL 支持一种hash索引类型:

CREATE INDEX strings_hash_idx ON "STRINGS_DB" USING hash (str);

适用于使用 的简单相等搜索=,就像您拥有它一样。我引用了有关限制的手册:

哈希索引操作目前没有 WAL 记录,因此在数据库崩溃后可能需要使用 REINDEX 重建哈希索引。它们也不会通过流式复制或基于文件的复制进行复制。由于这些原因,目前不鼓励使用散列索引。


对现实生活表的快速测试,433k 行,总共 59 MB:

SELECT * FROM tbl WHERE email = 'some.user@some.domain.com'
-- No index, sequnence scan: Total runtime: 188 ms  
-- B-tree index (default):   Total runtime:   0.046 ms  
-- Hash index:               Total runtime:   0.032 ms  

这不是很大,但有些东西。与我测试中的电子邮件地址相比,使用更长的字符串时差异会更大。创建索引只需 1 或 2 秒。与任一索引。

于 2012-07-12T19:40:54.750 回答
0

[编辑] 限制返回的结果返回它遇到的第一个满足条件的记录:对于 SqlServer:选择 TOP 1 ...;对于 mysql/postgres:选择 ... 限制 1;

如果可以有多个,也许在您的选择语句中添加“TOP 1”可能会更快返回。

String checkIfAlreadyScanned = "SELECT TOP 1 id FROM \"STRINGS_DB\"  where STR ='" + mystring + "'";

这样,它只需要找到字符串的第一个实例。

但是,如果您没有倍数,您可能不会看到这种方法有太多好处。

就像其他人所说的那样,在上面放一个索引可能会有所帮助。

于 2012-07-12T19:12:28.173 回答
0

假设您实际上不需要该id列,我认为这为编译器提供了最大的优化机会:

select 1
where exists(
    select 1 
    from STRINGS_DB
    where STR = 'MyString'
)
于 2012-07-12T19:13:50.450 回答
0

尽管这里的所有答案都有其优点,但我想提另一个方面。

以这种方式构建您的查询并传递一个字符串不会帮助数据库引擎优化您的查询。相反,您应该编写一个存储过程,通过一个参数调用它,让数据库引擎构建一个查询计划并重用您的命令。

当然该字段应该被索引

于 2012-07-12T19:15:35.627 回答