ToB企服应用市场:ToB评测及商务社交产业平台

标题: golang操纵sqlite3加速本地结构化数据查询 [打印本页]

作者: 玛卡巴卡的卡巴卡玛    时间: 2024-12-19 14:16
标题: golang操纵sqlite3加速本地结构化数据查询
摘要

在没有网络支持且仅能使用 Go 语言包的开辟环境中,如果需要频仍查询一个较大的 CSV 文件,有几种优化方案可以考虑。
每种方法都有其实用场景:对于小数据集和简单查询,map 存储足够快速且轻量;而对于较大数据集或复杂查询,SQLite 提供了更好的查询灵活性和性能。
Sqlite3

SQLite3 是一个开源的、轻量级的关系型数据库管理系统 (RDBMS),它的重要特点是将数据库引擎嵌入到应用程序中,而无需依赖服务器端数据库系统。SQLite3 以其小巧、高效、易于集成和高可靠性成为许多桌面、移动装备及嵌入式系统中常用的数据库办理方案。
SQLite3 的根本特点
SQLite3 的工作原理
SQLite3 将数据存储在单一的文件中,这个文件中不仅存储了数据,还包括了数据库的元数据(如表结构、索引等)。这种计划使得 SQLite3 对于需要将数据库存储在单一文件中的场景非常合适。
SQLite3 的数据库文件通常具有 .sqlite 或 .db 扩展名,且它是 按页存储数据的。SQLite3 使用 1024 字节(默认)为根本存储单元,每个页面可以存储一个数据记载、索引节点、事务日志等。
当应用程序需要实验 SQL 查询时,它通过 SQLite3 的 API 来与数据库文件进行交互。SQLite3 会实验 SQL 语句,操纵数据库文件,然后返回结果。
SQLite3 的上风
SQLite3 的常见用途

SQLite3 与其他数据库的比力
特性SQLite3MySQL/PostgreSQL安装与设置零设置,直接使用数据库文件需要安装和设置数据库服务服务器需求无需服务器,嵌入式数据库需要独立的数据库服务器性能对小型数据集性能良好大型数据集查询更高效事务支持完全支持 ACID 事务完全支持 ACID 事务扩展性得当小到中型应用,单用户查询支持高并发、多用户、大数据量使用场景本地存储、嵌入式、移动应用等企业级应用、复杂查询等 总而言之,SQLite3 是一个轻量级的、无服务器的数据库系统,非常得当嵌入式应用、桌面应用和移动应用。它通过将数据库引擎嵌入到应用程序中,使得开辟者可以或许快速实现数据存储功能,且不需要额外的数据库服务器或设置。对于小型到中型的数据存储需求,SQLite3 提供了高效、可靠的办理方案,同时也支持事务、ACID 原则等关系型数据库的紧张特性。
SQLite 命令

如果你在sqlite3官网下载了一个sqlite引擎,那么就可以像python一样在cmd敲下一个sqlite3命令,然后在交互式命令行中使用sqlite3。(笔者没有下载)
在 sqlite3 中,SQL 语句需以分号 ; 末端才会实验,允许跨行输入。特殊的点命令(如 .help 和 .tables)以小数点 . 开头,不需要分号末端。
  1. $ sqlite3
  2. SQLite version 3.3.6
  3. Enter ".help" for instructions
  4. sqlite>
复制代码
sqlite3的命令以.开头,如需获取可用的点命令的清单,可以输入 “.help”。例如:
  1. sqlite>.help
复制代码
上面的命令会显示各种紧张的 SQLite 点命令的列表,如下所示:
命令形貌.backup ?DB? FILE备份 DB 数据库(默认是 "main")到 FILE 文件。.bail ON|OFF发生错误后停止。默认为 OFF。.databases列出数据库的名称及其所依附的文件。.dump ?TABLE?以 SQL 文本格式转储数据库。如果指定了 TABLE 表,则只转储匹配 LIKE 模式的 TABLE 表。.echo ON|OFF开启或关闭 echo 命令。.exit退出 SQLite 提示符。.explain ON|OFF开启或关闭得当于 EXPLAIN 的输出模式。如果没有带参数,则为 EXPLAIN on,即开启 EXPLAIN。.header(s) ON|OFF开启或关闭头部显示。.help显示消息。.import FILE TABLE导入来自 FILE 文件的数据到 TABLE 表中。.indices ?TABLE?显示所有索引的名称。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表的索引。.load FILE ?ENTRY?加载一个扩展库。.log FILE|off开启或关闭日志。FILE 文件可以是 stderr(尺度错误)/stdout(尺度输出)。.mode MODE设置输出模式,MODE 可以是下列之一:     
.nullvalue STRING在 NULL 值的地方输出 STRING 字符串。.output FILENAME发送输出到 FILENAME 文件。.output stdout发送输出到屏幕。.print STRING...逐字地输出 STRING 字符串。.prompt MAIN CONTINUE更换尺度提示符。.quit退出 SQLite 提示符。.read FILENAME实验 FILENAME 文件中的 SQL。.schema ?TABLE?显示 CREATE 语句。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表。.separator STRING改变输出模式和 .import 所使用的分隔符。.show显示各种设置的当前值。.stats ON|OFF开启或关闭统计。.tables ?PATTERN?列出匹配 LIKE 模式的表的名称。.timeout MS尝试打开锁定的表 MS 毫秒。.width NUM NUM为 "column" 模式设置列宽度。.timer ON|OFF 开启或关闭 CPU 定时器。 使用 .show 命令,来检察 SQLite 命令提示符的默认设置。
  1. sqlite>.show
  2.      echo: off
  3.   explain: off
  4.   headers: off
  5.      mode: column
  6. nullvalue: ""
  7.    output: stdout
  8. separator: "|"
  9.     width:
  10. sqlite>
复制代码
可以使用下列的点命令来格式化输出为本教程下面所列出的格式:
  1. sqlite>.header on
  2. sqlite>.mode column
  3. sqlite>.timer on
  4. sqlite>
复制代码
上面设置将产生如下格式的输出:
  1. ID          NAME        AGE         ADDRESS     SALARY
  2. ----------  ----------  ----------  ----------  ----------
  3. 1           Paul        32          California  20000.0
  4. 2           Allen       25          Texas       15000.0
  5. 3           Teddy       23          Norway      20000.0
  6. 4           Mark        25          Rich-Mond   65000.0
  7. 5           David       27          Texas       85000.0
  8. 6           Kim         22          South-Hall  45000.0
  9. 7           James       24          Houston     10000.0
  10. CPU Time: user 0.000000 sys 0.000000
复制代码
SQLite 语法

SQLite 是不区分大小写的,但也有一些命令是大小写敏感的,比如 GLOB 和 glob 在 SQLite 的语句中有不同的含义。
SQLite 注释是附加的注释,可以在 SQLite 代码中添加注释以增长其可读性,他们可以出现在任何空白处,包括在表达式内和其他 SQL 语句的中心,但它们不能嵌套。
SQL 注释以两个连续的 “-” 字符(ASCII 0x2d)开始,并扩展至下一个换行符(ASCII 0x0a)或直到输入结束,以先到者为准。
您也可以使用 C 风格的注释,以 “/" 开始,并扩展至下一个 "/” 字符对或直到输入结束,以先到者为准。SQLite的注释可以超过多行。
  1. sqlite>.help
  2. -- 这是一个简单的注释
复制代码
所有的 SQLite 语句可以以任何关键字开始,如 SELECT、INSERT、UPDATE、DELETE、ALTER、DROP 等,所有的语句以分号 ; 结束,如:
  1. SQLite ANALYZE 语句:
  2. ANALYZE;
  3. or
  4. ANALYZE database_name;
  5. or
  6. ANALYZE database_name.table_name;
  7. SQLite AND/OR 子句:
  8. SELECT column1, column2....columnN
  9. FROM   table_name
  10. WHERE  CONDITION-1 {AND|OR} CONDITION-2;
  11. SQLite ALTER TABLE 语句:
  12. ALTER TABLE table_name ADD COLUMN column_def...;
  13. SQLite ALTER TABLE 语句(Rename):
  14. ALTER TABLE table_name RENAME TO new_table_name;
  15. SQLite ATTACH DATABASE 语句:
  16. ATTACH DATABASE 'DatabaseName' As 'Alias-Name';
  17. SQLite BEGIN TRANSACTION 语句:
  18. BEGIN;
  19. or
  20. BEGIN EXCLUSIVE TRANSACTION;
  21. SQLite BETWEEN 子句:
  22. SELECT column1, column2....columnN
  23. FROM   table_name
  24. WHERE  column_name BETWEEN val-1 AND val-2;
  25. SQLite COMMIT 语句:
  26. COMMIT;
  27. SQLite CREATE INDEX 语句:
  28. CREATE INDEX index_name
  29. ON table_name ( column_name COLLATE NOCASE );
  30. SQLite CREATE UNIQUE INDEX 语句:
  31. CREATE UNIQUE INDEX index_name
  32. ON table_name ( column1, column2,...columnN);
  33. SQLite CREATE TABLE 语句:
  34. CREATE TABLE table_name(
  35.    column1 datatype,
  36.    column2 datatype,
  37.    column3 datatype,
  38.    .....
  39.    columnN datatype,
  40.    PRIMARY KEY( one or more columns )
  41. );
  42. SQLite CREATE TRIGGER 语句:
  43. CREATE TRIGGER database_name.trigger_name
  44. BEFORE INSERT ON table_name FOR EACH ROW
  45. BEGIN
  46.    stmt1;
  47.    stmt2;
  48.    ....
  49. END;
  50. SQLite CREATE VIEW 语句:
  51. CREATE VIEW database_name.view_name  AS
  52. SELECT statement....;
  53. SQLite CREATE VIRTUAL TABLE 语句:
  54. CREATE VIRTUAL TABLE database_name.table_name USING weblog( access.log );
  55. or
  56. CREATE VIRTUAL TABLE database_name.table_name USING fts3( );
  57. SQLite COMMIT TRANSACTION 语句:
  58. COMMIT;
  59. SQLite COUNT 子句:
  60. SELECT COUNT(column_name)
  61. FROM   table_name
  62. WHERE  CONDITION;
  63. SQLite DELETE 语句:
  64. DELETE FROM table_name
  65. WHERE  {CONDITION};
  66. SQLite DETACH DATABASE 语句:
  67. DETACH DATABASE 'Alias-Name';
  68. SQLite DISTINCT 子句:
  69. SELECT DISTINCT column1, column2....columnN
  70. FROM   table_name;
  71. SQLite DROP INDEX 语句:
  72. DROP INDEX database_name.index_name;
  73. SQLite DROP TABLE 语句:
  74. DROP TABLE database_name.table_name;
  75. SQLite DROP VIEW 语句:
  76. DROP VIEW view_name;
  77. SQLite DROP TRIGGER 语句:
  78. DROP TRIGGER trigger_name
  79. SQLite EXISTS 子句:
  80. SELECT column1, column2....columnN
  81. FROM   table_name
  82. WHERE  column_name EXISTS (SELECT * FROM   table_name );
  83. SQLite EXPLAIN 语句:
  84. EXPLAIN INSERT statement...;
  85. or
  86. EXPLAIN QUERY PLAN SELECT statement...;
  87. SQLite GLOB 子句:
  88. SELECT column1, column2....columnN
  89. FROM   table_name
  90. WHERE  column_name GLOB { PATTERN };
  91. SQLite GROUP BY 子句:
  92. SELECT SUM(column_name)
  93. FROM   table_name
  94. WHERE  CONDITION
  95. GROUP BY column_name;
  96. SQLite HAVING 子句:
  97. SELECT SUM(column_name)
  98. FROM   table_name
  99. WHERE  CONDITION
  100. GROUP BY column_name
  101. HAVING (arithematic function condition);
  102. SQLite INSERT INTO 语句:
  103. INSERT INTO table_name( column1, column2....columnN)
  104. VALUES ( value1, value2....valueN);
  105. SQLite IN 子句:
  106. SELECT column1, column2....columnN
  107. FROM   table_name
  108. WHERE  column_name IN (val-1, val-2,...val-N);
  109. SQLite Like 子句:
  110. SELECT column1, column2....columnN
  111. FROM   table_name
  112. WHERE  column_name LIKE { PATTERN };
  113. SQLite NOT IN 子句:
  114. SELECT column1, column2....columnN
  115. FROM   table_name
  116. WHERE  column_name NOT IN (val-1, val-2,...val-N);
  117. SQLite ORDER BY 子句:
  118. SELECT column1, column2....columnN
  119. FROM   table_name
  120. WHERE  CONDITION
  121. ORDER BY column_name {ASC|DESC};
  122. SQLite PRAGMA 语句:
  123. PRAGMA pragma_name;
  124. For example:
  125. PRAGMA page_size;
  126. PRAGMA cache_size = 1024;
  127. PRAGMA table_info(table_name);
  128. SQLite RELEASE SAVEPOINT 语句:
  129. RELEASE savepoint_name;
  130. SQLite REINDEX 语句:
  131. REINDEX collation_name;
  132. REINDEX database_name.index_name;
  133. REINDEX database_name.table_name;
  134. SQLite ROLLBACK 语句:
  135. ROLLBACK;
  136. or
  137. ROLLBACK TO SAVEPOINT savepoint_name;
  138. SQLite SAVEPOINT 语句:
  139. SAVEPOINT savepoint_name;
  140. SQLite SELECT 语句:
  141. SELECT column1, column2....columnN
  142. FROM   table_name;
  143. SQLite UPDATE 语句:
  144. UPDATE table_name
  145. SET column1 = value1, column2 = value2....columnN=valueN
  146. [ WHERE  CONDITION ];
  147. SQLite VACUUM 语句:
  148. VACUUM;
  149. SQLite WHERE 子句:
  150. SELECT column1, column2....columnN
  151. FROM   table_name
  152. WHERE  CONDITION;
复制代码
SQLite 数据范例

每个存储在 SQLite 数据库中的值都具有以下存储类之一:、
存储类形貌NULL值是一个 NULL 值。INTEGER值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。REAL值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。TEXT值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。BLOB值是一个 blob 数据,完全根据它的输入存储。 SQLite 的数据范例并不是严格的范例约束。每一列的数据范例更准确地说是 存储类(storage class)。存储类定义了数据在 SQLite 中如何存储。
亲和范例形貌TEXT数值型数据在被插入之前,需要先被转换为文本格式,之后再插入到目标字段中。NUMERIC当文本数据被插入到亲缘性为NUMERIC的字段中时,如果转换操纵不会导致数据信息丢失以及完全可逆,那么SQLite就会将该文本数据转换为INTEGER或REAL范例的数据,如果转换失败,SQLite仍会以TEXT方式存储该数据。对于NULL或BLOB范例的新数据,SQLite将不做任何转换,直接以NULL或BLOB的方式存储该数据。需要额外阐明的是,对于浮点格式的常量文本,如"30000.0",如果该值可以转换为INTEGER同时又不会丢失数值信息,那么SQLite就会将其转换为INTEGER的存储方式。INTEGER对于亲缘范例为INTEGER的字段,其规则等同于NUMERIC,唯一差别是在实验CAST表达式时。REAL其规则根本等同于NUMERIC,唯一的差别是不会将"30000.0"这样的文本数据转换为INTEGER存储方式。NONE不做任何的转换,直接以该数据所属的数据范例进行存储。   SQLite支持列的亲和范例概念。任何列仍然可以存储任何范例的数据,当数据插入时,该字段的数据将会优先采取亲缘范例作为该值的存储方式,怎么明确呢?
列亲和范例——优先选择机制

SQLite 支持的 列的亲和范例(Affinity)概念是它独特的计划之一,它使得数据库系统具有非常灵活的范例管理机制。你可以将它明确为 范例优先级规则,它规定了在数据插入时,SQLite 会根据列的亲和范例来决定如何存储数据。尽管 SQLite 中的每列可以存储任何范例的数据,但它会尽大概根据列的亲和范例来决定命据的存储方式。这使得 SQLite 的数据范例系统相对宽松,也为灵活的数据存储提供了支持。
在 SQLite 中,每个列都有一个亲和范例,这并不是逼迫要求列只能存储某种数据范例,而是提供了一种“优先选择”机制。SQLite 会尽量将数据存储为列亲和范例所指定的范例,但如果存储的范例与亲和范例不匹配,SQLite 会尝试做须要的范例转换,最终以最得当的方式存储数据。
亲和范例与数据范例的区别
SQLite 的列本身并没有严格的数据范例约束,它的“范例”更多的是与 亲和范例 相关,而不是实际存储的数据范例。亲和范例 是一种范例约束,它定义了 SQLite 如何优先处置惩罚列中的数据。

SQLite 的数据范例系统是 动态范例 的,这意味着列的数据范例并不严格限定于某种范例,而是具有灵活性。每列的亲和范例告诉 SQLite 在数据插入时该使用什么优先范例来存储数据,但这并不意味着逼迫要求必须遵循某种数据范例。
亲和范例的优先级
SQLite 中支持的亲和范例包括:

当你插入数据时,SQLite 会根据列的亲和范例来确定命据存储的方式:
亲和范例的转换规则
SQLite 对每一列的存储范例提供了 范例亲和规则。这些规则决定了当你插入数据时,SQLite 会如何将数据转换为与列的亲和范例相匹配的格式。详细规则如下:

亲和范例示例
SQLite 创建数据库

SQLite 本身 不支持用户权限管理 或 权限控制 机制,因此不需要任何特殊的权限即可创建一个数据库
  1. $ sqlite3 DatabaseName.db
复制代码
通常情况下,数据库名称在 RDBMS 内应该是唯一的。
别的我们也可以在sqlite交互界面使用 .open 来建立新的数据库文件:
  1. sqlite>.open test.db
复制代码
打开已存在数据库也是用 .open 命令,以上命令如果 test.db 存在则直接会打开,不存在就创建它。
一旦数据库被创建,就可以使用 SQLite 的 .databases 命令来查抄它是否在数据库列表中,如下所示:
  1. sqlite>.databases
  2. seq  name             file
  3. ---  ---------------  ----------------------
  4. 0    main             /home/sqlite/testDB.db
复制代码
可以在命令提示符中使用 SQLite .dump 点命令来导出完整的数据库在一个文本文件中,如下所示:
  1. $sqlite3 testDB.db .dump > testDB.sql
复制代码
上面的命令将转换整个 testDB.db 数据库的内容到 SQLite 的语句中,并将其转储到 ASCII 文本文件 testDB.sql 中。您可以通过简单的方式从天生的 testDB.sql 恢复,如下所示:
  1. $sqlite3 testDB.db < testDB.sql
复制代码
SQLite 附加数据库

在 SQLite 中,附加数据库(Attach Database)允许在一个数据库连接中打开多个数据库文件。这个特性非常有效,尤其是在需要跨多个数据库进行查询的场景下。通过附加数据库,SQLite 允许你在一个查询中访问多个数据库,而不需要分别打开多个连接。
SQLite 使用 ATTACH DATABASE 命令将一个新的数据库文件附加到当前的数据库连接中。附加的数据库将拥有一个别名,后续的 SQL 查询可以通过该别名来引用附加的数据库。
  1. ATTACH DATABASE file_name AS database_name;
复制代码
如果想附加一个现有的数据库 testDB.db,则 ATTACH DATABASE 语句将如下所示:
  1. sqlite> ATTACH DATABASE 'testDB.db' as 'TEST';
复制代码
使用 SQLite .database 命令来显示附加的数据库。
  1. sqlite> .database
  2. seq  name             file
  3. ---  ---------------  ----------------------
  4. 0    main             /home/sqlite/testDB.db
  5. 2    test             /home/sqlite/testDB.db
复制代码
数据库名称 main 和 temp 被保留用于主数据库和存储临时表及其他临时数据对象的数据库。这两个数据库名称可用于每个数据库连接,且不应该被用于附加,否则将得到一个警告消息,如下所示:
  1. sqlite>  ATTACH DATABASE 'testDB.db' as 'TEMP';
  2. Error: database TEMP is already in use
  3. sqlite>  ATTACH DATABASE 'testDB.db' as 'main';
  4. Error: database main is already in use;
复制代码
然后,你可以在查询时通过数据库别名来引用附加的数据库:
  1. -- 从主数据库和附加数据库中查询数据
  2. SELECT * FROM main.users;
  3. SELECT * FROM TEST.orders;
复制代码
也可以进行跨表查询:
  1. -- 打开两个数据库并附加
  2. ATTACH DATABASE 'main.db' AS main;
  3. ATTACH DATABASE 'secondary.db' AS secondary;
  4. -- 从不同的数据库查询数据
  5. SELECT users.name, orders.order_date
  6. FROM main.users
  7. JOIN secondary.orders ON users.user_id = orders.user_id;
复制代码
SQLite 分离数据库

SQLite 的 DETACH DATABASE 语句是用来把定名数据库从一个数据库连接分离和游离出来,连接是之前使用 ATTACH 语句附加的。如果同一个数据库文件已经被附加上多个别名,DETACH 命令将只断开给定名称的连接,而其余的仍然有效。您无法分离 main 或 temp 数据库。
  1. DETACH DATABASE 'Alias-Name';
复制代码
假设在前面的章节中您已经创建了一个数据库,并给它附加了 ‘test’ 和 ‘currentDB’,使用 .database 命令,我们可以看到:
  1. sqlite>.databases
  2. seq  name             file
  3. ---  ---------------  ----------------------
  4. 0    main             /home/sqlite/testDB.db
  5. 2    test             /home/sqlite/testDB.db3    currentDB        /home/sqlite/testDB.db
复制代码
现在,让我们尝试把 ‘currentDB’ 从 testDB.db 中分离出来,如下所示:
  1. sqlite> DETACH DATABASE 'currentDB';
复制代码
现在,如果查抄当前附加的数据库,您会发现,testDB.db 仍与 ‘test’ 和 ‘main’ 保持连接。
  1. sqlite>.databases
  2. seq  name             file
  3. ---  ---------------  ----------------------
  4. 0    main             /home/sqlite/testDB.db
  5. 2    test             /home/sqlite/testDB.db
复制代码
SQLite 创建表

SQLite 的 CREATE TABLE 语句用于在任何给定的数据库创建一个新表。创建根本表,涉及到定名表、定义列及每一列的数据范例。
  1. CREATE TABLE database_name.table_name(
  2.    column1 datatype  PRIMARY KEY(one or more columns),
  3.    column2 datatype,
  4.    column3 datatype,
  5.    .....
  6.    columnN datatype,
  7. );
复制代码
CREATE TABLE 是告诉数据库系统创建一个新表的关键字。CREATE TABLE 语句后跟着表的唯一的名称或标识。您也可以选择指定带有 table_name 的 database_name。
而对于约束项,sqlite和mysql都差不多,可以参考mysql的建表语句。
下面是一个实例,它创建了一个 COMPANY 表,ID 作为主键,NOT NULL 的约束表示在表中创建纪录时这些字段不能为 NULL:
  1. sqlite> CREATE TABLE COMPANY(
  2.    ID INT PRIMARY KEY     NOT NULL,
  3.    NAME           TEXT    NOT NULL,
  4.    AGE            INT     NOT NULL,
  5.    ADDRESS        CHAR(50),
  6.    SALARY         REAL
  7. );
复制代码
可以使用 SQLIte 命令中的 .tables 命令来验证表是否已成功创建,该命令用于列出附加数据库中的所有表。
  1. sqlite>.tables
  2. COMPANY     DEPARTMENT
复制代码
可以使用 SQLite .schema 命令得到表的完整信息,如下所示:
  1. sqlite>.schema COMPANY
  2. CREATE TABLE COMPANY(
  3.    ID INT PRIMARY KEY     NOT NULL,
  4.    NAME           TEXT    NOT NULL,
  5.    AGE            INT     NOT NULL,
  6.    ADDRESS        CHAR(50),
  7.    SALARY         REAL
  8. );
复制代码
SQLite 删除表

SQLite 的 DROP TABLE 语句用来删除表定义及其所有相关数据、索引、触发器、约束和该表的权限规范。
  1. DROP TABLE database_name.table_name;
复制代码
SQLite Insert 语句

INSERT INTO 语句有两种根本语法,如下所示:
  1. # 在这里,column1, column2,...columnN 是要插入数据的表中的列的名称。
  2. INSERT INTO TABLE_NAME [(column1, column2, column3,...columnN)]  VALUES (value1, value2, value3,...valueN);
  3. # 如果要为表中的所有列添加值,您也可以不需要在 SQLite 查询中指定列名称。但要确保值的顺序与列在表中的顺序一致。SQLite 的 INSERT INTO 语法如下:
  4. INSERT INTO TABLE_NAME VALUES (value1,value2,value3,...valueN);
复制代码
假设已经在 testDB.db 中创建了 COMPANY表,如下所示:
  1. sqlite> CREATE TABLE COMPANY(
  2.    ID INT PRIMARY KEY     NOT NULL,
  3.    NAME           TEXT    NOT NULL,
  4.    AGE            INT     NOT NULL,
  5.    ADDRESS        CHAR(50),
  6.    SALARY         REAL
  7. );
复制代码
现在,下面的语句将在 COMPANY 表中创建六个记载:
  1. INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)
  2. VALUES (1, 'Paul', 32, 'California', 20000.00 );
  3. INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)
  4. VALUES (2, 'Allen', 25, 'Texas', 15000.00 );
  5. INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)
  6. VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );
  7. INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)
  8. VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );
  9. INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)
  10. VALUES (5, 'David', 27, 'Texas', 85000.00 );
  11. INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)
  12. VALUES (6, 'Kim', 22, 'South-Hall', 45000.00 );
复制代码
也可以使用第二种语法在 COMPANY 表中创建一个记载,如下所示:
  1. INSERT INTO COMPANY VALUES (7, 'James', 24, 'Houston', 10000.00 );
复制代码
您可以通过在一个有一组字段的表上使用 select 语句,填凑数据到另一个表中。下面是语法:
  1. INSERT INTO first_table_name [(column1, column2, ... columnN)]
  2.    SELECT column1, column2, ...columnN
  3.    FROM second_table_name
  4.    [WHERE condition];
复制代码
SQLite Select 语句

SQLite 的 SELECT 语句的根本语法如下:
  1. SELECT column1, column2, columnN FROM table_name;
复制代码
因为所有的点命令只在 SQLite 提示符中可用,以是当您进行带有 SQLite 的编程时,您要使用下面的带有 sqlite_master 表的 SELECT 语句来列出所有在数据库中创建的表:
  1. sqlite> SELECT tbl_name FROM sqlite_master WHERE type = 'table';
复制代码
可以列出关于 COMPANY 表的完整信息,如下所示:
  1. sqlite> SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = 'COMPANY';
复制代码
  1. CREATE TABLE COMPANY(
  2.    ID INT PRIMARY KEY     NOT NULL,
  3.    NAME           TEXT    NOT NULL,
  4.    AGE            INT     NOT NULL,
  5.    ADDRESS        CHAR(50),
  6.    SALARY         REAL
  7. )
复制代码
SQLite 运算符

运算符是一个保留字或字符,重要用于 SQLite 语句的 WHERE 子句中实验操纵,如比力和算术运算。
SQLite 算术运算符

假设变量 a=10,变量 b=20,则:
运算符形貌实例+加法 - 把运算符双方的值相加 a + b 将得到 30-减法 - 左操纵数减去右操纵数 a - b 将得到 -10*乘法 - 把运算符双方的值相乘 a * b 将得到 200/除法 - 左操纵数除以右操纵数 b / a 将得到 2%取模 - 左操纵数除以右操纵数后得到的余数 b % a 将得到 0 下面是 SQLite 算术运算符的简单实例:
  1. sqlite> .mode line
  2. sqlite> select 10 + 20;
  3. 10 + 20 = 30
  4. sqlite> select 10 - 20;
  5. 10 - 20 = -10
  6. sqlite> select 10 * 20;
  7. 10 * 20 = 200
  8. sqlite> select 10 / 5;
  9. 10 / 5 = 2
  10. sqlite> select 12 %  5;
  11. 12 %  5 = 2
复制代码
SQLite 比力运算符

假设变量 a=10,变量 b=20,则:
运算符形貌实例==查抄两个操纵数的值是否相等,如果相等则条件为真。 (a == b) 不为真。=查抄两个操纵数的值是否相等,如果相等则条件为真。 (a = b) 不为真。!=查抄两个操纵数的值是否相等,如果不相等则条件为真。 (a != b) 为真。<>查抄两个操纵数的值是否相等,如果不相等则条件为真。 (a <> b) 为真。>查抄左操纵数的值是否大于右操纵数的值,如果是则条件为真。 (a > b) 不为真。<查抄左操纵数的值是否小于右操纵数的值,如果是则条件为真。 (a < b) 为真。>=查抄左操纵数的值是否大于等于右操纵数的值,如果是则条件为真。 (a >= b) 不为真。<=查抄左操纵数的值是否小于等于右操纵数的值,如果是则条件为真。 (a <= b) 为真。!<查抄左操纵数的值是否不小于右操纵数的值,如果是则条件为真。 (a !< b) 为假。!>查抄左操纵数的值是否不大于右操纵数的值,如果是则条件为真。 (a !> b) 为真。 SQLite 逻辑运算符

下面是 SQLite 中所有的逻辑运算符列表。
运算符形貌ANDAND 运算符允许在一个 SQL 语句的 WHERE 子句中的多个条件的存在。BETWEENBETWEEN 运算符用于在给定最小值和最大值范围内的一系列值中搜刮值。EXISTSEXISTS 运算符用于在满意一定条件的指定表中搜刮行的存在。ININ 运算符用于把某个值与一系列指定列表的值进行比力。NOT ININ 运算符的对立面,用于把某个值与不在一系列指定列表的值进行比力。LIKELIKE 运算符用于把某个值与使用通配符运算符的相似值进行比力。GLOBGLOB 运算符用于把某个值与使用通配符运算符的相似值进行比力。GLOB 与 LIKE 不同之处在于,它是大小写敏感的。NOTNOT 运算符是所用的逻辑运算符的对立面。比如 NOT EXISTS、NOT BETWEEN、NOT IN,等等。它是否定运算符。OROR 运算符用于结合一个 SQL 语句的 WHERE 子句中的多个条件。IS NULLNULL 运算符用于把某个值与 NULL 值进行比力。ISIS 运算符与 = 相似。IS NOTIS NOT 运算符与 != 相似。||连接两个不同的字符串,得到一个新的字符串。UNIQUEUNIQUE 运算符搜刮指定表中的每一行,确保唯一性(无重复)。 假设 COMPANY 表有以下记载:
  1. ID          NAME        AGE         ADDRESS     SALARY
  2. ----------  ----------  ----------  ----------  ----------
  3. 1           Paul        32          California  20000.0
  4. 2           Allen       25          Texas       15000.0
  5. 3           Teddy       23          Norway      20000.0
  6. 4           Mark        25          Rich-Mond   65000.0
  7. 5           David       27          Texas       85000.0
  8. 6           Kim         22          South-Hall  45000.0
  9. 7           James       24          Houston     10000.0
复制代码
下面的 SELECT 语句列出了 AGE 大于等于 25 且工资大于等于 65000.00 的所有记载:
  1. sqlite> SELECT * FROM COMPANY WHERE AGE >= 25 AND SALARY >= 65000;
  2. ID          NAME        AGE         ADDRESS     SALARY
  3. ----------  ----------  ----------  ----------  ----------
  4. 4           Mark        25          Rich-Mond   65000.0
  5. 5           David       27          Texas       85000.0
复制代码
下面的 SELECT 语句列出了 AGE 不为 NULL 的所有记载,结果显示所有的记载,意味着没有一个记载的 AGE 等于 NULL:
  1. sqlite>  SELECT * FROM COMPANY WHERE AGE IS NOT NULL;ID          NAME        AGE         ADDRESS     SALARY
  2. ----------  ----------  ----------  ----------  ----------
  3. 1           Paul        32          California  20000.0
  4. 2           Allen       25          Texas       15000.0
  5. 3           Teddy       23          Norway      20000.0
  6. 4           Mark        25          Rich-Mond   65000.0
  7. 5           David       27          Texas       85000.0
  8. 6           Kim         22          South-Hall  45000.0
  9. 7           James       24          Houston     10000.0
复制代码
下面的 SELECT 语句列出了 NAME 以 ‘Ki’ 开始的所有记载,‘Ki’ 之后的字符不做限定:
  1. sqlite> SELECT * FROM COMPANY WHERE NAME LIKE 'Ki%';
  2. ID          NAME        AGE         ADDRESS     SALARY
  3. ----------  ----------  ----------  ----------  ----------
  4. 6           Kim         22          South-Hall  45000.0
复制代码
下面的 SELECT 语句列出了 NAME 以 ‘Ki’ 开始的所有记载,‘Ki’ 之后的字符不做限定:
  1. sqlite> SELECT * FROM COMPANY WHERE NAME GLOB 'Ki*';
  2. ID          NAME        AGE         ADDRESS     SALARY
  3. ----------  ----------  ----------  ----------  ----------
  4. 6           Kim         22          South-Hall  45000.0
复制代码
下面的 SELECT 语句列出了 AGE 的值为 25 或 27 的所有记载:
  1. sqlite> SELECT * FROM COMPANY WHERE AGE IN ( 25, 27 );
  2. ID          NAME        AGE         ADDRESS     SALARY
  3. ----------  ----------  ----------  ----------  ----------
  4. 2           Allen       25          Texas       15000.0
  5. 4           Mark        25          Rich-Mond   65000.0
  6. 5           David       27          Texas       85000.0
复制代码
面的 SELECT 语句列出了 AGE 的值既不是 25 也不是 27 的所有记载:
  1. sqlite> SELECT * FROM COMPANY WHERE AGE NOT IN ( 25, 27 );
  2. ID          NAME        AGE         ADDRESS     SALARY
  3. ----------  ----------  ----------  ----------  ----------
  4. 1           Paul        32          California  20000.0
  5. 3           Teddy       23          Norway      20000.0
  6. 6           Kim         22          South-Hall  45000.0
  7. 7           James       24          Houston     10000.0
复制代码
下面的 SELECT 语句列出了 AGE 的值在 25 与 27 之间的所有记载:
  1. sqlite> SELECT * FROM COMPANY WHERE AGE BETWEEN 25 AND 27;
  2. ID          NAME        AGE         ADDRESS     SALARY
  3. ----------  ----------  ----------  ----------  ----------
  4. 2           Allen       25          Texas       15000.0
  5. 4           Mark        25          Rich-Mond   65000.0
  6. 5           David       27          Texas       85000.0
复制代码
下面的 SELECT 语句使用 SQL 子查询,子查询查找 SALARY > 65000 的带有 AGE 字段的所有记载,后边的 WHERE 子句与 EXISTS 运算符一起使用,列出了外查询中的 AGE 存在于子查询返回的结果中的所有记载:
  1. sqlite> SELECT AGE FROM COMPANY
  2.         WHERE EXISTS (SELECT AGE FROM COMPANY WHERE SALARY > 65000);
  3. AGE
  4. ----------
  5. 32
  6. 25
  7. 23
  8. 25
  9. 27
  10. 22
  11. 24
复制代码
SQLite 位运算符

位运算符作用于位,并逐位实验操纵。真值表 & 和 | 如下:
pqp & qp | q0000010111111001 假设如果 A = 60,且 B = 13,现在以二进制格式,它们如下所示:
  1. A = 0011 1100
  2. B = 0000 1101
  3. A&B = 0000 1100
  4. A|B = 0011 1101
  5. ~A  = 1100 0011
复制代码
下表中列出了 SQLite 语言支持的位运算符。假设变量 A=60,变量 B=13,则:
运算符形貌实例&如果同时存在于两个操纵数中,二进制 AND 运算符复制一位到结果中。 (A & B) 将得到 12,即为 0000 1100|如果存在于任一操纵数中,二进制 OR 运算符复制一位到结果中。 (A | B) 将得到 61,即为 0011 1101~二进制补码运算符是一元运算符,具有"翻转"位效应,即0酿成1,1酿成0。 (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码情势。<<二进制左移运算符。左操纵数的值向左移动右操纵数指定的位数。 A << 2 将得到 240,即为 1111 0000>> 二进制右移运算符。左操纵数的值向右移动右操纵数指定的位数。 A >> 2 将得到 15,即为 0000 1111 下面的实例演示了 SQLite 位运算符的用法:
  1. sqlite> .mode line
  2. sqlite> select 60 | 13;
  3. 60 | 13 = 61
  4. sqlite> select 60 & 13;
  5. 60 & 13 = 12
  6. sqlite>  select  (~60);
  7. (~60) = -61
  8. sqlite>  select  (60 << 2);
  9. (60 << 2) = 240
  10. sqlite>  select  (60 >> 2);
  11. (60 >> 2) = 15
复制代码
SQLite 表达式

…更多内容可以参考官方文档或者菜鸟教程:SQLite 表达式
golang操纵sqlite

golang操纵sqlite有两个比力知名的库:modernc.org/sqlite 和 github.com/mattn/go-sqlite3
modernc.org/sqlite 和 github.com/mattn/go-sqlite3 都是 Go 语言中的 SQLite 库,用于与 SQLite 数据库交互。这两个库尤其各自的特性:
选择哪个库取决于你的项目需求、环境限定以及对性能和功能的详细要求:

golang操纵sqlite3加速本地结构化数据查询

回归正题,假设现在有一个CSV文件,现在为了测试其查询性能我们准备两个大小不一的CSV,而且实现开头提到的三种查询方式。

在此之前,我们先用golang实现一个测时的装饰器:
  1. package main
  2. import (
  3.         "fmt"
  4.         "time"
  5. )
  6. func WithTimer(f func()) func() {
  7.         return func() {
  8.                 start := time.Now()
  9.                 // do something
  10.                 f()
  11.                 // 打印输出耗时
  12.                 fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
  13.         }
  14. }
  15. func A() {
  16.         time.Sleep(time.Second * 3)
  17. }
  18. func main() {
  19.         WithTimer(A)()
  20. }
  21. // 程序输出
  22. // 3.00s elapsed
复制代码
然后我们新建一个文件夹准备实现Reader接口,

需要用到的三方库如下:
为了功能的实现,代码坚固性暂时不在考虑范围内,因此笔者会忽略掉大部门error处置惩罚。
SimpleReader

简单的遍历查询:
  1. // simpleReader实现的是普通遍历的方法
  2. type SimpleReader struct {
  3.         fd   *os.File
  4.         rd   *csv.Reader
  5.         head []string
  6. }
  7. func NewSimpleReader(filename string) *SimpleReader {
  8.         fp, _ := os.Open(filename)
  9.         reader := csv.NewReader(fp)
  10.         head, _ := reader.Read()
  11.         return &SimpleReader{fd: fp, rd: reader, head: head}
  12. }
  13. func (r *SimpleReader) Search(field string, value string) []string {
  14.         fieldIndex := slices.Index(r.head, field)
  15.         for {
  16.                 record, err := r.rd.Read()
  17.                 if err == io.EOF {
  18.                         break
  19.                 }
  20.                 if err != nil {
  21.                         log.Fatal(err)
  22.                 }
  23.                 if record[fieldIndex] == value {
  24.                         return record
  25.                 }
  26.         }
  27.         return nil
  28. }
  29. func (r *SimpleReader) Close() {
  30.         _ = r.fd.Close()
  31. }
复制代码
我们来测试一下它的1000次查询耗时:
  1. const (
  2.         smallField  = "field9"
  3.         smallValue  = "value6_8"
  4.         bigField    = "field9"
  5.         bigValue    = "value99855_8"
  6.         searchCount = 1000
  7. )
  8. func main() {
  9.         WithTimer(func() {
  10.                 for i := 0; i < searchCount; i++ {
  11.                         rd := reader.NewSimpleReader("small.csv")
  12.                         _ = rd.Search(smallField, smallValue)
  13.                         rd.Close()
  14.                 }
  15.         })()
  16.         WithTimer(func() {
  17.                 for i := 0; i < searchCount; i++ {
  18.                         rd2 := reader.NewSimpleReader("big.csv")
  19.                         _ = rd2.Search(bigField, bigValue)
  20.                         rd2.Close()
  21.                 }
  22.         })()
  23. }
  24. 0.03s elapsed
  25. 33.64s elapsed
复制代码
因为simpleReader没有持久化数据,以是每次查询都得将数据重新加载后遍历,对于大文件来说查询速率显著降落。
MapReader

相较于SimpleReader,MapReader增长了一个内存缓存,
  1. type MapReader struct {
  2.         head  []string
  3.         cache map[int][]string
  4. }
  5. func NewMapReader(filename string) *MapReader {
  6.         fp, _ := os.Open(filename)
  7.         defer fp.Close()
  8.        
  9.         reader := csv.NewReader(fp)
  10.         head, _ := reader.Read()
  11.         cache := make(map[int][]string)
  12.         index := 0
  13.         for {
  14.                 record, err := reader.Read()
  15.                 if err == io.EOF {
  16.                         break
  17.                 }
  18.                 if err != nil {
  19.                         log.Fatal(err)
  20.                 }
  21.                 cache[index] = record
  22.                 index++
  23.         }
  24.         return &MapReader{head: head, cache: cache}
  25. }
  26. func (reader *MapReader) Search(field string, value string) []string {
  27.         index := slices.Index(reader.head, field)
  28.         for _, line := range reader.cache {
  29.                 if line[index] == value {
  30.                         return line
  31.                 }
  32.         }
  33.         return nil
  34. }
  35. func (reader *MapReader) Close() {
  36.         reader.head = nil
  37.         reader.cache = nil
  38. }
复制代码
我们来测试一下它的1000次查询耗时:
  1. const (
  2.         smallField  = "field9"
  3.         smallValue  = "value6_8"
  4.         bigField    = "field9"
  5.         bigValue    = "value99855_8"
  6.         searchCount = 1000
  7. )
  8. func main() {
  9.         WithTimer(func() {
  10.                 rd := reader.NewMapReader("small.csv")
  11.                 for i := 0; i < searchCount; i++ {
  12.                         rd.Search(smallField, smallValue)
  13.                 }
  14.                 rd.Close()
  15.         })()
  16.         WithTimer(func() {
  17.                 rd2 := reader.NewMapReader("big.csv")
  18.                 for i := 0; i < searchCount; i++ {
  19.                         _ = rd2.Search(bigField, bigValue)
  20.                 }
  21.                 rd2.Close()
  22.         })()
  23. }
  24. 0.05s elapsed
  25. 3.04s elapsed
复制代码
可以看到,在大文件的查询上速率显著变快,这归功于运行运行内存的高速运转(和redis一样),在内存中遍历自然比不断从硬盘中取出数据再遍历快的多,缺陷就是占用大量内存,在小内存的机器上大概触发系统级别的“内存逐出”。
sqlReader

sqlReader则将数据存储到sqlite中:
  1. type SqliteReader struct {
  2.         rd            *csv.Reader
  3.         sqliteHandler *sqlx.DB // Sql操作抓手
  4.         lineLength    int      // 行长度
  5.         head          []string // 头信息
  6. }
  7. const (
  8.         sqlitePath      = "csv.db"
  9.         sqliteTableName = "test"
  10. )
  11. func NewSqliteReader(filename string) *SqliteReader {
  12.         // 读权限打开csv
  13.         fp, _ := os.Open(filename)
  14.         defer fp.Close()
  15.         reader := csv.NewReader(fp)
  16.         // 增加csv reader容错性
  17.         reader.LazyQuotes = true
  18.         reader.TrimLeadingSpace = true
  19.         // 读取头信息
  20.         head, _ := reader.Read()
  21.         // 打开sqlite
  22.         db, _ := sqlx.Open("sqlite3", sqlitePath)
  23.         rd := &SqliteReader{
  24.                 rd:            reader,
  25.                 head:          head,
  26.                 sqliteHandler: db,
  27.                 lineLength:    len(head),
  28.         }
  29.         if err := rd.initDB(); err != nil {
  30.                 log.Fatal(err)
  31.         }
  32.         fmt.Println("DB构建结束")
  33.         return rd
  34. }
  35. func (reader *SqliteReader) initDB() error {
  36.         // 拼接建库语句
  37.         fieldDefine := make([]string, reader.lineLength)
  38.         for index, field := range reader.head {
  39.                 fieldDefine[index] = fmt.Sprintf("%s TEXT", field)
  40.         }
  41.         fieldDefinedString := strings.Join(fieldDefine, ",")
  42.         // 建立数据库
  43.         var createTableSQL = fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (
  44.     id INTEGER PRIMARY KEY AUTOINCREMENT,
  45.     %s
  46.     );`, sqliteTableName, fieldDefinedString)
  47.         fmt.Println(createTableSQL)
  48.         _, err := reader.sqliteHandler.Exec(createTableSQL)
  49.         if err != nil {
  50.                 return err
  51.         }
  52.         // 拼接插入语句
  53.         insertSQL := fmt.Sprintf("INSERT INTO %s VALUES (NULL,", sqliteTableName)
  54.         for i := 0; i < reader.lineLength; i++ {
  55.                 insertSQL += "?,"
  56.         }
  57.         insertSQL = strings.TrimSuffix(insertSQL, ",") + ");"
  58.         // 预编译提速
  59.         stmt, _ := reader.sqliteHandler.Prepare(insertSQL)
  60.         // 使用事务插入数据
  61.         tx, _ := reader.sqliteHandler.Begin()
  62.         // 写入数据
  63.         for {
  64.                 line, err := reader.rd.Read()
  65.                 if err == io.EOF {
  66.                         break
  67.                 }
  68.                 if err != nil {
  69.                         return err
  70.                 }
  71.                 // 转为接口类型
  72.                 values := make([]interface{}, reader.lineLength)
  73.                 for i, v := range line {
  74.                         values[i] = v
  75.                 }
  76.                 // 插入sql
  77.                 _, err = stmt.Exec(values...)
  78.                 if err != nil {
  79.                         return err
  80.                 }
  81.         }
  82.         _ = tx.Commit()
  83.         return nil
  84. }
  85. func (reader *SqliteReader) Search(field string, value string) []any {
  86.         query := fmt.Sprintf(`SELECT * FROM %s WHERE %s==? LIMIT 1`, sqliteTableName, field)
  87.         // 查询
  88.         row, _ := reader.sqliteHandler.Queryx(query, value)
  89.         defer row.Close()
  90.         // 读取结果
  91.         for row.Next() {
  92.                 r, _ := row.SliceScan()
  93.                 return r
  94.         }
  95.         return nil
  96. }
  97. func (reader *SqliteReader) Close() {
  98.         _ = reader.sqliteHandler.Close()
  99. }
复制代码
我们来测试一下它的1000次查询耗时:
  1. func main() {
  2.         //WithTimer(func() {
  3.         //        rd := reader.NewSqliteReader("small.csv")
  4.         //        for i := 0; i < searchCount; i++ {
  5.         //                r := rd.Search(smallField, smallValue)
  6.         //                fmt.Println(r)
  7.         //        }
  8.         //        rd.Close()
  9.         //})()
  10.         WithTimer(func() {
  11.                 rd2 := reader.NewSqliteReader("big.csv")
  12.                 for i := 0; i < searchCount; i++ {
  13.                         _ = rd2.Search(bigField, bigValue)
  14.                 }
  15.                 rd2.Close()
  16.         })()
  17. }
  18. 0.14s elapsed
  19. 143.41s elapsed
复制代码
总结

从上文测试结果来看,sqlite优化后的查询效率甚至都不如遍历查询,而缓存查询的效果虽然最好,但最费内存,尤其是在某些场景下大概机器只有区区1-2G,根本跑不起来。
我们对sqlite进行优化:
最后优化如下:
  1. func (reader *SqliteReader) initDB(handler *csv.Reader) (err error) {
  2.         // 拼接建库语句
  3.         fieldDefine := make([]string, reader.lineLength)
  4.         for index, field := range reader.head {
  5.                 fieldDefine[index] = fmt.Sprintf("%s TEXT", field)
  6.         }
  7.         fieldDefinedString := strings.Join(fieldDefine, ",")
  8.         // 建立数据库
  9.         var createTableSQL = fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s (
  10.     id INTEGER PRIMARY KEY AUTOINCREMENT,
  11.     %s
  12.     );`, sqliteTableName, fieldDefinedString)
  13.         csvLogger.Infof("create database [%s] : %s.\n", sqliteTableName, createTableSQL)
  14.         _, err = reader.sqliteHandler.Exec(createTableSQL)
  15.         if err != nil {
  16.                 return err
  17.         }
  18.         // 开启事务
  19.         tx, err := reader.sqliteHandler.Begin()
  20.         if err != nil {
  21.                 return err
  22.         }
  23.         // 此处不能用内部声明的err,因此defer关键词会把此时的快照入栈
  24.         // 此时err必然不为空,因此需要声明返回值的err
  25.         // golang执行顺序
  26.         // 1. returned_value = return 此处是err
  27.         // 2. defer出栈
  28.         // 3. 返回returned_value 此处是err
  29.         defer func() {
  30.                 if err != nil {
  31.                         _ = tx.Rollback()
  32.                 } else {
  33.                         _ = tx.Commit()
  34.                 }
  35.         }()
  36.         // 拼接插入语句
  37.         bulkSize, currentSize := 1000, 0
  38.         values := make([]any, 0, bulkSize)
  39.         for {
  40.                 line, err := handler.Read()
  41.                 if err == io.EOF {
  42.                         break
  43.                 }
  44.                 if err != nil {
  45.                         return err
  46.                 }
  47.                 // 追加批量插入的值
  48.                 values = append(values, line)
  49.                 // 计数自增
  50.                 currentSize++
  51.                 // 判断是否需要批量新增
  52.                 if currentSize == bulkSize {
  53.                         condition, args, err := sqlx.In(
  54.                                 fmt.Sprintf(
  55.                                         "INSERT INTO %s (%s) VALUES %s",
  56.                                         sqliteTableName,
  57.                                         strings.Join(reader.head, ","),
  58.                                         strings.Repeat("(?),", currentSize),
  59.                                 ),
  60.                                 values...,
  61.                         )
  62.                         if err != nil {
  63.                                 csvLogger.Errorln(err)
  64.                                 return err
  65.                         }
  66.                         // 构建正确sql语句
  67.                         condition = strings.TrimSuffix(condition, ",") + ";"
  68.                         if _, err = reader.sqliteHandler.Exec(condition, args...); err != nil {
  69.                                 return err
  70.                         }
  71.                         // 归零计数器
  72.                         currentSize = 0
  73.                         // 清空value
  74.                         values = make([]any, 0, bulkSize)
  75.                 }
  76.         }
  77.         // 判断漏网之鱼
  78.         if currentSize > 0 {
  79.                 condition, args, err := sqlx.In(
  80.                         fmt.Sprintf(
  81.                                 "INSERT INTO %s (%s) VALUES %s",
  82.                                 sqliteTableName,
  83.                                 strings.Join(reader.head, ","),
  84.                                 strings.Repeat("(?),", currentSize),
  85.                         ),
  86.                         values...,
  87.                 )
  88.                 if err != nil {
  89.                         csvLogger.Errorln(err)
  90.                         return err
  91.                 }
  92.                 // 构建正确sql语句
  93.                 condition = strings.TrimSuffix(condition, ",") + ";"
  94.                 if _, err = reader.sqliteHandler.Exec(condition, args...); err != nil {
  95.                         return err
  96.                 }
  97.                 // 清空value
  98.                 values = nil
  99.         }
  100.         return nil
  101. }
复制代码
然后根据我的场景增大查询次数到10W(我数据量准备了10W左右的,我的场景是数量等于查询次数):
  1. CREATE TABLE IF NOT EXISTS test (
  2.     id INTEGER PRIMARY KEY AUTOINCREMENT,
  3.     field1 TEXT,field2 TEXT,field3 TEXT,field4 TEXT,field5 TEXT,field6 TEXT,field7 TEXT,field8 TEXT,field9 TEXT,field10 TEXT
  4.     );
  5. DB构建结束
  6. 2.28s elapsed
复制代码
可以看到查询效果非常不错,提速了百倍有余,而遍历查询此时因为太久不出结果被我Ctrl+C掉了。。。
盘算一下二者查询速率的决定因素,假设查询次数为n,单次查询时间为t:
轶闻趣事:SQLite 背后的故事

SQLite 现在已经是全球用户最多的数据库产品。它非常小巧以及单文件无单独操纵系统进程,就像病毒一样依附在宿主程序的进程里运行。你看不到它,但它却无处不在。汽车,手机,浏览器,以及各类 app 里都能见到 .db 末端的 SQLite 数据库文件。 如果 SQLite 出现庞大 bug,或者像平常的数据库那样无法连接,整个地球都会乱套。你身边用的险些所有电子产品(手机,电脑,iPad,笔记本)和嵌入式装备全部都会出题目。它的诞生到大范围全球流行的过程和一般软件有着不太一样的发展进程。
SQLite 诞生的契机就是典型的程序员开辟的故事剧本。作者 Richard 最开始在一艘军舰上做 contractor(就是我们说的外包)。他们程序跑在军舰安装的电脑上,电脑上装的是 informix。Richard 的工作就是把 informix 的数据拿出来进行盘算然后展示到电脑屏幕上(这和我们本日的 CRUD 工作雷同)。比力令人恼火的是 informix 很不稳定,经常瓦解连不上。部队里的铁拳长官可不懂啥 TCP/IP 或者数据库系统知识。他们只看到软件的报错 dialog(对话框) 经常弹出来,而这个 dialog 又是 SQLite 的作者 Richard 写的软件画出来的,锅自然从天而降。于是 Richard 决定本身重新写一个无需外部连接的数据库来办理这个题目。
…文章泉源:https://liyafu.com/2022-07-31-sqlite-untold-story/

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4