CHAR_INT和INT主键之间的查询速度差异是否很大?

时间:2019-05-15 15:29:16

标签: sql database sqlite relational-database

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

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

此外,为字符串的CHAR部分创建数字ID会更有效吗?因此,使用前面的示例:ABC_12345将变成类似于1_12345。我知道它们都是字符串,只是想知道仅使用数字是否有效率。

我正在使用SQLite。

谢谢!

3 个答案:

答案 0 :(得分:4)

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

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

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

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

答案 1 :(得分:3)

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

特别是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”,则将其分配   整数亲和力。

     

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

     

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

     

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

     

否则,关联性为NUMERIC。

     

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

Datatypes In SQLite

说类型相似性并不能决定数据的存储方式。每列均根据取决于要存储的数据的存储类进行存储。

空值存储为空值,一串数字(作为字符串包含或不作为字符串)存储为整数。简而言之,数据将按照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 函数返回列的类型(存储类型不是列亲和力)

结果显示为:-

enter image description here enter image description here enter image description here

使用除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 _ ????例如:-

    • enter image description here
  • mytable2 的主键为?? _ ????例如:-

    • enter image description here
  • mytable3 mytable1 的副本,但该表已使用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上。

除了前面的链接之外,您可能还希望查看:-

答案 2 :(得分:1)

Sqlite有两种类型的表。

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

因此,您的CHAR_INT列(Sqlite非常 宽容列类型需要什么;这只是hint关于如何尝试存储和比较存储在其中的值的WITHOUT ROWID该列(不是实际类型),根据Sqlite规则,具有整数 affinity ,但是由于ABC_123之类的内容无法无损地转换为整数,因此它们将以字符串形式存储。插入一行意味着更新主表和主键索引(当然还有其他索引)。通过键查找一行涉及到首先在索引中查找对应的rowid,然后查找主表的该行。从正面来看,这两个查询都使用O(log N)二进制搜索。

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

最终哪个更好取决于一系列因素,例如表中使用了多少其他索引,行中存储了多少数据,在表上运行的查询等等。该文档建议,除其他建议外,还可以构建带有WITHOUT ROWID表和不带有#I load df and make some changes with it movies <- read_csv("~/shared/mi_2018/data/movies.csv") %>% select(movie_id, production_countries) movies_with_countries = extract_json2(df = movies, col = "production_countries") a = spread(movies_with_countries, key = production_countries_sep, value = production_countries_v) row.names(a) <- a$movie_id a = a %>% select(-movie_id, -production_countries) a[is.na(a)] <- 0 #Here I make a bipartite graph, but the result is reverse compared to what I expected bg = graph.incidence(a) V(bg)$type pr = bipartite.projection(bg) q = get.adjacency(pr$proj1,sparse=FALSE,attr="weight") plot(pr$proj1,edge.width=E(pr$proj1)$weight^2,edge.color="black",vertex.label=V(pr$proj1)$name) 表的数据库,并进行基准测试,以了解更适合特定用途的数据。