【问题标题】:Is there a big querying speed difference between CHAR_INT and INT primary keys?CHAR_INT 和 INT 主键之间的查询速度差异很大吗?
【发布时间】:2019-10-02 19:26:42
【问题描述】:

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

我将有大量数据(+10 亿条记录)并且查询和插入速度是优先考虑的问题,使用 CHAR_INT 主键会对速度产生很大影响吗?还是相对可以忽略不计?

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

我正在使用 SQLite。

谢谢!

【问题讨论】:

    标签: sql database sqlite relational-database


    【解决方案1】:

    据我所知,没有内置数据类型“CHAR_INT”类型。

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

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

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

    【讨论】:

    • SQLite 几乎可以接受任何数据类型。例如,create table awesome(a_id gordon_linoff primary key); 将成功。 (耸肩)
    • @MikeSherrill'CatRecall' 。 . .哇!我不知道。我学到了一些东西。
    【解决方案2】:

    type(类型相似性)只有一个例外,没有真正的区别。

    例外情况是 the_column_name INTEGER PRIMARY KEY(有或没有 AUTOINCREMENT),它将列定义为 rowid 列的别名。 INT PRIMARY KEY 没有。

    所以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 作为一种类型会通过所有规则,除了最后一条规则,如果前面的规则都不适用,则类型关联为 NUMERIC。

    3.1.柱亲和性的测定

    列的亲和性由列的声明类型决定,根据 以下规则按所示顺序:

    如果声明的类型包含字符串“INT”,那么它被赋值 INTEGER 亲和力。

    如果列的声明类型包含任何字符串“CHAR”, “CLOB”或“TEXT”,则该列具有 TEXT 亲和性。请注意, 类型 VARCHAR 包含字符串“CHAR”,因此被分配了 TEXT 亲和力。

    如果声明的列类型包含字符串“BLOB”,或者如果没有 指定类型,则该列具有亲和性 BLOB。

    如果列的声明类型包含任何字符串“REAL”, "FLOA" 或 "DOUB" 则该列具有 REAL 亲和力。

    否则,亲和度为 NUMERIC。

    请注意,确定列亲和性的规则的顺序是 重要的。声明类型为“CHARINT”的列将同时匹配 规则 1 和 2,但第一个规则优先,因此该列 亲和力将是整数。

    Datatypes In SQLite

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

    Null 存储为 null,一串数字(封闭或不封闭为字符串)作为整数。简而言之,数据将按照 SQLite 确定的方式存储,并且 SQlite 将尝试尽可能高效地存储数据,并且在尽可能小的空间内存储到一个字节作为存储的最小单位。

    考虑以下几点:-

    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(如果未注释并运行)失败并出现 UNIQUE 冲突,因为 SQLite 认为 12345 与“12345”相同。

    typeof 函数返回列的类型(存储类型不是列亲和性)

    结果如下:-

    使用除 INTEGER PRIMARY KEY 之外的任何东西(有一些派生),因此 rowid 的别名是

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

      • 搜索具有特定 rowid 的记录或具有指定范围内的 rowid 的所有记录的速度大约是通过指定任何其他 PRIMARY KEY 或索引值进行的类似搜索的两倍。 ROWIDs and the INTEGER PRIMARY KEY

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

    搜索一个索引,比较快,相对于数据本身来说,数据比较少,而且数据本身是只读的。

    也许考虑以下几点:-

    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 的副本,但该表是使用 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
    
    • 我相信 mytable3 会受到一些影响,因为扫描(在这种情况下)是在 PRIMARY KEY 上,而不是适合/首选其他两个的 rowid。李>

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

    【讨论】:

      【解决方案3】:

      Sqlite 有两种类型的表。

      默认ROWID table。行表是B*-Trees,有一个有符号的 64 位整数作为它们的主键(rowid)。如果该列有一个 INTEGER PRIMARY KEY 列,则该列用作 rowid 的别名。任何其他 PRIMARY KEY 类型,或两列或多列的复合主键,都只是一个唯一索引。

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

      另一个表类型是WITHOUT ROWID。这些表使用与索引相同的普通 B-Tree 数据结构,并使用表的主键(无论其类型或列数)作为真正的主键。插入只需要更新一张表(当然还有额外的索引),查找只需要搜索一张表,因此当您的主键不是INTEGER 时,它可以更快并且占用更少的磁盘空间。

      最终哪个更好取决于一系列因素,例如表中使用了多少其他索引、一行中存储了多少数据、在表上运行的查询以及许多其他因素。除了其他建议外,该文档还建议构建带有和不带有 WITHOUT ROWID 表的数据库,并进行基准测试以查看更适合特定用途的内容。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-20
        • 2010-12-17
        • 1970-01-01
        • 2021-11-06
        • 2011-06-05
        • 2023-03-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多