CHAR_INT 和 INT 主键的查询速度差别大吗?

Is there a big querying speed difference between CHAR_INT and INT primary keys?

我知道通常最佳做法是将 INTEGER 字段用作主键,但不幸的是,由于我正在使用 API,我只能使用格式如下的主键: CHAR_INT(例如:ABC_12345)。

我会有很多数据(+10 亿条记录),查询和插入速度是首要任务,使用 CHAR_INT 主键对速度有很大影响吗?还是相对可以忽略不计?

此外,为字符串的 CHAR 部分创建数字 ID 会更有效吗?所以使用前面的例子:ABC_12345 会变成类似 1_12345 的东西。我知道它们都是字符串,只是想知道只使用数字是否有任何效率。

我正在使用 SQLite。

谢谢!

据我所知没有built-in数据类型"CHAR_INT"类型。

但是,SQLite 在类型上非常灵活,并且允许任何字符串作为类型的名称。 SQLite 不是强类型的,因此该值似乎存储为字符串。

数字索引效率更高。一个重要的原因是数字是固定长度的。字符串是可变长度的,这会增加在索引中存储键值时的开销。另一个原因是硬件在支持数字比较方面做得更好。当考虑字符集和排序规则时,字符串比较变得更加复杂。

也就是说,与使用索引的好处相比,搜索和维护索引的开销实际上很小。所以,我不会担心索引只有字符串。但是,我会更担心施加此类限制的工具。您应该能够在表中选择所需的键。

Sqlite 有两种类型的 tables。

默认ROWID table. Rowid tables are B*-Trees, with a signed 64-bit integer as their primary key (The rowid). If the column has a single INTEGER PRIMARY KEY列,此列用作rowid的别名。任何其他 PRIMARY KEY 类型,或两列或多列的复合主键,都只是一个唯一索引。

所以您的 CHAR_INT 列(Sqlite 非常 宽容列类型;它只是 hint 关于如何尝试存储并比较存储在该列中的值,而不是实际类型),根据 Sqlite 规则,具有整数 affinity,但由于 ABC_123 之类的东西无法无损地转换为整数,它们被存储为字符串。插入一行意味着更新主 table 和主键索引(当然还有任何其他索引)。按键查找一行涉及首先在索引中查找相应的 rowid,然后查找主 table 的那一行。从好的方面来说,两个查找都使用 O(log N) 二进制搜索。

另一个 table 类型是 WITHOUT ROWID。这些 table 使用与索引相同的普通 B-Tree 数据结构,并使用 table 的主键,无论其类型或列数如何,作为真正的主键。插入只需要更新一个table(当然还有额外的索引),查找只需要搜索一个table,所以当你的主键是不是 INTEGER.

最终哪个更好取决于一系列因素,例如 table 使用了多少其他索引,一行中存储了多少数据,查询 运行在 tables 和许多其他事情上。该文档建议,除其他建议外,在有和没有 WITHOUT ROWID table 的情况下构建数据库,并进行基准测试以查看什么更适合 table 用于特定用途。

类型(类型亲和力)只有一个例外,没有什么区别。

异常是 the_column_name INTEGER PRIMARY KEY(带或不带 AUTOINCREMENT),它将列定义为 rowid 列的别名。 INT PRIMARY KEY 没有。

  • 注意编码 AUTOINCREMENT 有相当大的影响,例如

所以 the_column_name CHAR_INT PRIMARY KEYthe_column_name INT CHAR PRIMARY KEY 甚至 the_column_name INT PRIMARY KEY 实际上是相同的,甚至可以使用 the_column_name RUMPLESTILTSKIN PRIMARY KEY(尽管后者会有不同的类型关联)。

决定类型亲和力的是规则。有 5 个。具有最高优先级的规则是,如果类型具有 INT,则类型关联为 INTEGER。 RUMPLESTILTSKIN 作为一种类型将通过所有规则,但最后一条除外,即如果先前规则的 none 适用,则类型亲和力为 NUMERIC。

3.1. Determination Of Column Affinity

The affinity of a column is determined by the declared type of the column, according to the following rules in the order shown:

If the declared type contains the string "INT" then it is assigned INTEGER affinity.

If the declared type of the column contains any of the strings "CHAR", "CLOB", or "TEXT" then that column has TEXT affinity. Notice that the type VARCHAR contains the string "CHAR" and is thus assigned TEXT affinity.

If the declared type for a column contains the string "BLOB" or if no type is specified then the column has affinity BLOB.

If the declared type for a column contains any of the strings "REAL", "FLOA", or "DOUB" then the column has REAL affinity.

Otherwise, the affinity is NUMERIC.

Note that the order of the rules for determining column affinity is important. A column whose declared type is "CHARINT" will match both rules 1 and 2 but the first rule takes precedence and so the column affinity will be INTEGER.

Datatypes In SQLite

说类型亲和性并不能决定数据的存储方式。每一列都根据存储 class 进行存储,这取决于所存储的数据。

Null 存储为 null,一串数字(包含或不包含为字符串)作为整数。简而言之,数据将按照 SQLite 的决定进行存储,SQlite 将尝试尽可能高效地存储数据,并且尽可能少 space 到一个字节,这是存储的最小单位。

考虑以下:-

DROP TABLE IF EXISTS mytable1;
DROP TABLE IF EXISTS mytable2;
DROP TABLE IF EXISTS mytable3;
CREATE TABLE IF NOT EXISTS mytable1 (c1 CHAR_INT PRIMARY KEY); 
CREATE TABLE IF NOT EXISTS mytable2 (c1 INT PRIMARY KEY); 
CREATE TABLE IF NOT EXISTS mytable3 (c1 RUMPLEstiltSkin PRIMARY KEY);
-- INSERT INTO mytable1 VALUES(12345),('12345'),('a_12345'),('1_12345'),(x'0102030405'); -- fails due to unique constraint 12345 and '12345' are the same 
-- INSERT INTO mytable2 VALUES(12345),('12345'),('a_12345'),('1_12345'),(x'0102030405'); -- fails due to unique constraint 12345 and '12345' are the same 
-- INSERT INTO mytable3 VALUES(12345),('12345'),('a_12345'),('1_12345'),(x'0102030405'); -- fails due to unique constraint 12345 and '12345' are the same 
INSERT INTO mytable1 VALUES(12345),('54321'),('a_12345'),('1_12345'),(x'0102030405');
INSERT INTO mytable2 VALUES(12345),('54321'),('a_12345'),('1_12345'),(x'0102030405');
INSERT INTO mytable3 VALUES(12345),('54321'),('a_12345'),('1_12345'),(x'0102030405');
SELECT c1, typeof(c1) FROM mytable1;
SELECT c1, typeof(c1) FROM mytable2;
SELECT c1, typeof(c1) FROM mytable3;
  • 注释掉的 INSERTS(如果未注释且 运行)因唯一冲突而失败,因为 SQLite 认为 12345 与“12345”相同。

typeof 函数returns 列的类型(存储类型不是列关联)

结果如下:-

不使用 INTEGER PRIMARY KEY(有一些派生),因此 rowid 的别名是

  • 大约快一半
  • 有两个索引,rowid(除非TABLE定义为WITHOUT ROWID)和PRIMARY KEY。

    • Searching for a record with a specific rowid, or for all records with rowids within a specified range is around twice as fast as a similar search made by specifying any other PRIMARY KEY or indexed value. ROWIDs and the INTEGER PRIMARY KEY

  • 处理数字而不是字符串会消耗更多space,因此会减少缓冲区中可以保存的数据,因此会有一些影响。

搜索索引,比较快,相对于数据本身,数据比较少,数据本身只是读取。

或许考虑以下:-

DROP TABLE IF EXISTS mytable1;
DROP TABLE IF EXISTS mytable2;
DROP TABLE IF EXISTS mytable3;
CREATE TABLE IF NOT EXISTS mytable1 (pk INT PRIMARY KEY, name TEXT); 
CREATE TABLE IF NOT EXISTS mytable2 (pk CHAR_INT PRIMARY KEY, name TEXT); 
CREATE TABLE IF NOT EXISTS mytable3 (pk INT PRIMARY KEY, name TEXT) WITHOUT ROWID; 

INSERT INTO mytable1
    WITH RECURSIVE cte1(a,b) AS (
            SELECT 'ABC_'||CAST(abs(random()) AS TEXT),'some data' UNION ALL 
            SELECT DISTINCT (substr(a,1,4))||CAST(abs(random()) AS TEXT),'some data' FROM cte1 LIMIT 1000000
        )
    SELECT * FROM cte1
;

INSERT INTO mytable2
    WITH RECURSIVE cte1(a,b) AS (
            SELECT '1_'||CAST(abs(random()) AS TEXT),'some data' UNION ALL 
            SELECT DISTINCT (abs(random()) % 100)||'_'||CAST(abs(random()) AS TEXT),'some data' FROM cte1 LIMIT 1000000
        )
    SELECT * FROM cte1
;
INSERT INTO mytable3 SELECT * FROM mytable1;

SELECT * FROM mytable1 WHERE name LIKE('%me data%');
SELECT * FROM mytable2 WHERE name LIKE('%me data%');
SELECT * FROM mytable3 WHERE name LIKE('%me data%');

SELECT * FROM mytable3 WHERE name LIKE('%me data%');
SELECT * FROM mytable1 WHERE name LIKE('%me data%');
SELECT * FROM mytable2 WHERE name LIKE('%me data%');

SELECT * FROM mytable2 WHERE name LIKE('%me data%');
SELECT * FROM mytable3 WHERE name LIKE('%me data%');
SELECT * FROM mytable1 WHERE name LIKE('%me data%');

这将创建 3 个排列,所有排列均包含 1,000,000 行

  • mytable1的主键是ABC_????例如:-

  • mytable2 的主键是 ??_????例如:-

  • mytable3mytable1 的副本,但是table 已使用 WITHOUT ROWID

  • 定义

计时

所有 3 个的 SELECTS 都非常接近(完成多个选择并以不同的顺序进行缓存)。包含时间的消息是:-

SELECT * FROM mytable1 WHERE name LIKE('%me data%')
> OK
> Time: 0.672s


SELECT * FROM mytable2 WHERE name LIKE('%me data%')
> OK
> Time: 0.667s


SELECT * FROM mytable3 WHERE name LIKE('%me data%')
> OK
> Time: 0.702s


SELECT * FROM mytable3 WHERE name LIKE('%me data%')
> OK
> Time: 0.7s


SELECT * FROM mytable1 WHERE name LIKE('%me data%')
> OK
> Time: 0.675s


SELECT * FROM mytable2 WHERE name LIKE('%me data%')
> OK
> Time: 0.673s


SELECT * FROM mytable2 WHERE name LIKE('%me data%')
> OK
> Time: 0.676s


SELECT * FROM mytable3 WHERE name LIKE('%me data%')
> OK
> Time: 0.709s


SELECT * FROM mytable1 WHERE name LIKE('%me data%')
> OK
> Time: 0.676s
  • 我相信我的table3,因为扫描(在这种情况下)是在 PRIMARY KEY 上,而不是 rowid suitable/preferable另外两个。

除了之前的链接,您不妨看看:-