背景
细节可以参考这篇文章: 与copilot 结对编程系列 - log日志重复性检测 - 第1篇 - 总体介绍及效果展示
这个模块主要用于将处理后的日志数据存储到 SQLite 数据库中。以下是对详细解释.
数据策略选择
主要有以下需要选择
- 数据结构定义
- 数据库选择
- 数据库 table 选择
- 数据库插入方式选择
数据结构定义
在当前场景中,combined_sct_case_data
、combined_log_info_data
和 combined_sct_case_log_data
作为数据库存储的入参是为了将不同来源的数据整合并存储到数据库中。这些数据通常来自不同的日志文件或系统,整合后可以更方便地进行查询和分析。以下是每个数据集的具体作用:
-
combined_sct_case_data:
- 这个数据集包含了 SCT (System Configuration Test) 案例的基本信息,如案例名称、ID 等。
- 存储这些数据有助于在查询时快速获取特定 SCT 案例的相关信息。
-
combined_log_info_data:
- 这个数据集包含了日志的详细信息,如日志的过程、级别、类型、文件行号和日志消息等。
- 存储这些数据有助于在查询时获取特定日志的详细信息,并进行日志级别和类型的统计分析。
-
combined_sct_case_log_data:
- 这个数据集包含了 SCT 案例与日志之间的关联信息,如重复行数、不同日志级别的计数等。
- 存储这些数据有助于在查询时分析特定 SCT 案例的日志重复情况,以及不同日志级别的分布情况。
通过将这些数据整合并存储到数据库中,可以实现以下几个目的:
- 数据整合:将来自不同来源的数据整合到一个统一的数据库中,方便后续的查询和分析。
- 高效查询:通过 SQL 查询,可以高效地获取和分析特定 SCT 案例或日志的详细信息。
- 数据分析:可以对日志数据进行统计分析,如日志级别的分布、重复行数的统计等,帮助发现系统中的问题。
总之,将 combined_sct_case_data
、combined_log_info_data
和 combined_sct_case_log_data
作为数据库存储的入参,可以实现数据的整合、存储和高效查询,方便后续的分析和处理。
数据库选择
结合当前的使用场景, 最后使用SQLite作为存储数据库。
主要有以下几个原因:
-
轻量级和嵌入式:
- SQLite是一个轻量级的嵌入式数据库,不需要单独的服务器进程。它将整个数据库存储在一个单一的文件中,非常适合小型应用程序或嵌入式系统。
- 这种特性使得SQLite非常适合脚本和小型项目,因为它不需要复杂的安装和配置。
-
易于部署:
- SQLite数据库是一个单一的文件,这使得它非常容易部署和备份。只需复制这个文件即可迁移数据库。
- 对于需要在不同环境中运行的脚本(如开发、测试和生产环境),SQLite的这种特性非常有用。
-
零配置:
- SQLite几乎不需要配置。与MySQL等需要配置服务器、用户权限等不同,SQLite开箱即用,减少了配置和维护的复杂性。
-
适合单用户或低并发场景:
- SQLite适用于单用户或低并发的应用场景。对于当前场景,通常不会有大量的并发访问,因此SQLite的性能足够满足需求。
- MySQL等数据库更适合高并发和多用户的场景,但对于当前场景来说可能是过度设计。
-
开发和测试方便:
- 在开发和测试阶段,使用SQLite可以快速进行数据库操作,而不需要启动和管理一个MySQL服务器。
- 这使得开发人员可以更专注于业务逻辑,而不是数据库管理。
-
内存占用小:
- SQLite的内存占用非常小,适合资源有限的环境。
- 对于需要在内存中进行大量计算或处理的脚本,SQLite的低内存占用是一个优势。
综上所述,SQLite的轻量级、易于部署、零配置、适合单用户或低并发场景以及开发和测试方便等特性,使得它成为当前场景的理想选择。相比之下,MySQL等数据库虽然功能更强大,但在这些场景中可能显得过于复杂和不必要。
数据库 table 选择
在脚本场景中,构建 create_sct_case_table_sql
、create_log_info_table_sql
和 create_sct_case_log_table_sql
这三张表是为了有效地存储和查询日志数据。以下是每张表的详细解释,包括其成员、主键、外键和索引的作用。
sct_case
表
SQL 语句
CREATE TABLE IF NOT EXISTS sct_case (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
filename TEXT
);
解释
- id: 主键,自动递增,用于唯一标识每个测试用例。
- name: 测试用例的名称,非空字段。
- filename: 关联的文件名。
作用
- 存储每个测试用例的基本信息。
- 主键
id
用于唯一标识每个测试用例,方便在其他表中引用。
log_info
表
SQL 语句
CREATE TABLE IF NOT EXISTS log_info (
id INTEGER PRIMARY KEY AUTOINCREMENT,
log_procedure TEXT,
log_level TEXT,
log_type TEXT,
file_line INTEGER,
log_message TEXT
);
解释
- id: 主键,自动递增,用于唯一标识每条日志信息。
- log_procedure: 日志记录的过程名称。
- log_level: 日志级别(如
DBG
,INF
,WRN
,ERR
)。 - log_type: 日志类型。
- file_line: 日志记录所在的文件行号。
- log_message: 日志消息内容。
作用
- 存储每条日志的详细信息。
- 主键
id
用于唯一标识每条日志,方便在其他表中引用。
sct_case_log
表
SQL 语句
CREATE TABLE IF NOT EXISTS sct_case_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sct_case_id INTEGER,
log_info_id INTEGER,
duplicate_lines_count INTEGER,
DBG INTEGER,
INF INTEGER,
WRN INTEGER,
ERR INTEGER,
OTHER INTEGER,
FOREIGN KEY (sct_case_id) REFERENCES sct_case (id),
FOREIGN KEY (log_info_id) REFERENCES log_info (id)
);
解释
- id: 主键,自动递增,用于唯一标识每条记录。
- sct_case_id: 外键,引用
sct_case
表的id
,表示该日志属于哪个测试用例。 - log_info_id: 外键,引用
log_info
表的id
,表示具体的日志信息。 - duplicate_lines_count: 重复行数统计。
- DBG: 调试级别日志的数量。
- INF: 信息级别日志的数量。
- WRN: 警告级别日志的数量。
- ERR: 错误级别日志的数量。
- OTHER: 其他类型日志的数量。
作用
- 存储每个测试用例的日志统计信息。
- 外键
sct_case_id
和log_info_id
用于建立与sct_case
和log_info
表的关联,确保数据的完整性和一致性。
总结
通过这三张表的设计,可以实现以下功能:
- 数据规范化:将测试用例、日志信息和日志统计分开存储,减少数据冗余。
- 数据完整性:使用外键确保数据之间的关联关系,避免孤立数据。
- 高效查询:通过索引和主键加速查询操作,尤其是在进行复杂的联表查询时。
这种设计方式使得日志数据的存储和查询更加高效和灵活,能够满足复杂的日志分析需求。
数据库插入方式选择
在当前脚本场景中,选择了分块插入数据的方式(insert_sct_case_chunk
、insert_log_info_chunk
、insert_sct_case_log_batch
)来提升插入速度。以下是具体的解释:
分块插入数据的原因
-
减少数据库连接开销:每次数据库操作(如插入、更新、删除)都会有一定的连接和断开连接的开销。通过分块插入,可以减少连接和断开连接的次数,从而降低开销。
-
批量插入优化:大多数数据库系统对批量插入进行了优化,能够更高效地处理成批的数据插入操作。相比逐条插入,批量插入可以显著提升性能。
-
事务处理:分块插入可以将多个插入操作放在一个事务中,减少事务的提交次数,从而提高插入速度。
提升插入速度的具体实现
-
chunked_iterable
:这是一个生成器函数,用于将一个可迭代对象分成多个小块。通过将大数据集分成小块,可以更高效地进行批量插入。 -
insert_sct_case_chunk
:这个函数用于批量插入sct_case
表的数据。通过将数据分块后批量插入,减少了单次插入的数据量,提高了插入效率。 -
insert_log_info_chunk
:这个函数用于批量插入log_info
表的数据。与insert_sct_case_chunk
类似,通过分块插入提高了插入速度。 -
insert_sct_case_log_batch
:这个函数用于批量插入sct_case_log
表的数据。通过批量插入,可以更高效地处理大量日志数据的插入。 -
store_sct_logs_to_db
:这个函数是整个数据存储过程的入口。它调用了上述分块插入函数,将数据分块后批量插入数据库。
提高存储速度的其他方法
多线程 (Multithreading)
通过多线程可以并行处理多个插入任务,进一步提高数据插入的效率。每个线程可以处理一部分数据,减少单个线程的负担。
使用 concurrent.futures.ThreadPoolExecutor
进行多线程操作,提升数据处理和插入速度。
在 store_logs_to_db
函数中,分别为 log_info_data
和 sct_case_data
创建了多个线程进行并行处理。
数据库模式设置 (Database Mode)
设置数据库模式为 PRAGMA synchronous = OFF 和 PRAGMA journal_mode = MEMORY 可以减少磁盘 I/O 操作,提高写入速度。这些设置会牺牲一定的安全性和数据完整性,但在批量插入的场景下是可以接受的。
在创建数据库连接时,设置了 PRAGMA journal_mode=WAL
和 PRAGMA synchronous=OFF
,以提高写入性能。
PRAGMA journal_mode=WAL
作用:将数据库的日志模式设置为 Write-Ahead Logging (WAL) 模式。
优点:
- 并发性:允许多个读操作同时进行,而不阻塞写操作。
- 性能:在大多数情况下,WAL 模式比传统的回滚日志模式性能更好。
- 恢复性:在崩溃后,WAL 模式可以更快地恢复数据库。
PRAGMA synchronous=OFF
作用:将数据库的同步模式设置为 OFF。
优点:
- 性能:显著提高写操作的性能,因为它减少了磁盘 I/O 操作。
缺点: - 数据安全性:在系统崩溃或断电的情况下,可能会导致数据丢失,因为写操作不会立即同步到磁盘。
代码示例
以下是一个简化的示例,展示了如何通过分块插入数据来提升插入速度:
def create_connection(db_file):
"""Create a database connection to the SQLite database specified by db_file."""
conn = sqlite3.connect(db_file)
conn.execute("PRAGMA journal_mode=WAL;")
conn.execute("PRAGMA synchronous=OFF;")
return conn
def chunked_iterable(iterable, size):
"""将可迭代对象分成多个小块"""
it = iter(iterable)
while True:
chunk = list(itertools.islice(it, size))
if not chunk:
break
yield chunk
def insert_sct_case_chunk(conn, sct_cases):
"""批量插入sct_case表的数据"""
with conn:
conn.executemany('INSERT INTO sct_case (id, name) VALUES (?, ?)', sct_cases)
def insert_log_info_chunk(conn, log_infos):
"""批量插入log_info表的数据"""
with conn:
conn.executemany('INSERT INTO log_info (id, log_procedure, log_level, log_type, file_line, log_message) VALUES (?, ?, ?, ?, ?, ?)', log_infos)
def insert_sct_case_log_batch(conn, sct_case_logs):
"""批量插入sct_case_log表的数据"""
with conn:
conn.executemany('INSERT INTO sct_case_log (sct_case_id, log_info_id, duplicate_lines_count, DBG, INF, WRN, ERR, OTHER) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', sct_case_logs)
def store_sct_logs_to_db(conn, sct_cases, log_infos, sct_case_logs, chunk_size=100):
"""将数据分块后批量插入数据库"""
for chunk in chunked_iterable(sct_cases, chunk_size):
insert_sct_case_chunk(conn, chunk)
for chunk in chunked_iterable(log_infos, chunk_size):
insert_log_info_chunk(conn, chunk)
for chunk in chunked_iterable(sct_case_logs, chunk_size):
insert_sct_case_log_batch(conn, chunk)
总结
通过批量插入、多线程处理和设置数据库模式,可以显著提高数据插入的速度。这些方法减少了数据库连接和事务处理的开销,提高了整体效率。
最后
有兴趣, 需要源码可以关注我的公众号, 代码的更多细节, 一起交流~~~
下一篇讲网页UI细节: 与copilot 结对编程系列 - log日志重复性检测 - 第4篇 - 网页UI界面