0

0

优化Python中SQLite3并发读写性能与最佳实践

霞舞

霞舞

发布时间:2025-11-15 12:32:46

|

821人浏览过

|

来源于php中文网

原创

优化Python中SQLite3并发读写性能与最佳实践

python应用中,sqlite3数据库的并发读写操作常因其默认锁定机制而引发性能瓶颈。本文旨在提供一套全面的优化策略,涵盖索引创建、wal模式启用、连接复用、批量插入等关键技术,并强调参数化查询、时间戳数据类型优化及合理异常处理等最佳实践,旨在提升sqlite3在多进程/多线程环境下的稳定性和效率。

理解SQLite3的并发限制与默认行为

SQLite3以其轻量级和无服务器架构广受欢迎,但在并发访问方面,其默认行为可能导致性能问题。在默认的Journal模式下,SQLite3在写入操作期间会对整个数据库文件进行独占锁定。这意味着当一个进程正在写入数据时,其他所有读写进程都将被阻塞,直到写入完成并释放锁。即使是读取操作,也会以共享模式锁定数据库,允许多个读取者同时访问,但会阻止任何写入者。当读写操作频繁且并发发生时,这种锁定机制就可能导致读取被跳过或操作超时。虽然可以通过设置连接超时(timeout)和忙碌超时(pragma busy_timeout)来等待锁释放,但这仅是缓解症状,治本之道在于提升数据库操作本身的效率并优化并发机制。

核心性能优化策略

为了有效解决SQLite3的并发读写瓶颈,以下策略至关重要:

1. 创建高效索引

索引是提升数据库查询性能的基石。没有索引,数据库在执行查询时可能需要扫描整个表,这对于大型表而言会极其耗时。通过为频繁查询的列添加索引,可以显著加快数据检索速度。

针对示例中的读取查询:

立即学习Python免费学习笔记(深入)”;

SELECT *
FROM table1
WHERE device_id='%s'
  AND payload_timestamp_utc=(
        SELECT MAX(payload_timestamp_utc)
        FROM table1
        WHERE device_id='%s'
  )
  AND start_time_utc < '%s' AND end_time_utc > '%s'
ORDER BY start_time_utc ASC

此查询频繁使用 device_id 和 payload_timestamp_utc 进行过滤和查找最大值。因此,一个复合索引在 (device_id, payload_timestamp_utc) 上将极大地提升查询效率。

创建索引示例:

import sqlite3

def create_index(db_path):
    conn = None
    try:
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        # 为 device_id 和 payload_timestamp_utc 创建复合索引
        cursor.execute('''
            CREATE INDEX IF NOT EXISTS idx_device_timestamp
            ON table1 (device_id, payload_timestamp_utc);
        ''')
        conn.commit()
        print("索引创建成功或已存在。")
    except sqlite3.Error as e:
        print(f"创建索引时发生错误: {e}")
    finally:
        if conn:
            conn.close()

# 在数据库初始化时或首次运行时调用
# create_index('./database1.db')

这个索引将使内部的 SELECT MAX(...) 子查询和外部查询的 WHERE 条件查找变得几乎即时。

2. 启用WAL(Write-Ahead Log)模式

WAL模式是SQLite3提供的一种高级日志记录机制,旨在改善并发性能。在WAL模式下,读写操作可以同时进行,互相之间不会阻塞。写入操作会将更改记录到一个单独的WAL文件中,而读取操作则可以直接从主数据库文件读取,如果需要最新的数据,则会合并WAL文件中的内容。

启用WAL模式示例: 在建立数据库连接后,执行以下PRAGMA命令:

import sqlite3

def enable_wal_mode(conn):
    try:
        cursor = conn.cursor()
        cursor.execute("PRAGMA journal_mode = WAL;")
        conn.commit()
        print("已启用WAL模式。")
    except sqlite3.Error as e:
        print(f"启用WAL模式时发生错误: {e}")

# 在每个数据库连接建立后立即调用
# con = sqlite3.connect(db_path, timeout=20)
# enable_wal_mode(con)

注意事项:

  • WAL模式会创建额外的 -wal 和 -shm 文件,请确保文件系统支持原子写入。
  • WAL模式并非万能药,如果写入操作非常频繁且持续时间长,仍然可能对读取造成轻微影响,但相比默认模式,并发性已大幅提升。

3. 复用数据库连接

频繁地打开和关闭数据库连接会带来不必要的性能开销。每次建立连接都需要进行文件I/O、资源分配等操作。在多进程或多线程应用中,最佳实践是每个进程或线程维护一个独立的数据库连接,并尽可能地复用该连接。

优化后的连接管理示例:

# Script2中的读取函数优化
import sqlite3
import pandas as pd
import os

# 假设 conn 是在进程启动时创建并传递进来的
def db_read_function_optimized(conn, param1, param2, param3):
    temp_df = None
    try:
        cursor = conn.cursor()
        # 注意:PRAGMA busy_timeout 可以在连接建立时设置一次,无需每次执行
        # cursor.execute('''pragma busy_timeout=10000''')

        # 使用参数化查询,防止SQL注入
        query = '''
            SELECT * FROM table1
            WHERE device_id=?
              AND payload_timestamp_utc=(SELECT MAX(payload_timestamp_utc) FROM table1 WHERE device_id=?)
              AND start_time_utc?
            ORDER BY start_time_utc ASC
        '''
        # pandas.read_sql 同样支持参数化查询
        temp_df = pd.read_sql(query, conn, params=(param1, param1, param3, param2))
    except sqlite3.Error as e:
        print(f"读取数据时发生错误: {e}")
    return temp_df

# Script1中的写入函数优化
def db_insert_function_optimized(conn, row):
    lastrowid = None
    try:
        cursor = conn.cursor()
        # cursor.execute('''pragma busy_timeout=10000''')
        sql = '''INSERT INTO table1(site_name,payload_timestamp_utc,device_id,start_time_utc,end_time_utc,
        value) VALUES(?,?,?,?,?,?) '''
        cursor.execute(sql, row)
        conn.commit() # 每次插入后提交事务
        lastrowid = cursor.lastrowid
    except sqlite3.Error as e:
        print(f"插入数据时发生错误: {e}")
    return lastrowid

# 在应用启动时创建连接,并在整个生命周期中复用
# db_path = os.path.join(os.getcwd(), './database1.db')
# conn_script1 = sqlite3.connect(db_path, timeout=20)
# enable_wal_mode(conn_script1) # 启用WAL模式
# # ... script1使用 conn_script1 ...
# conn_script1.close()

# conn_script2 = sqlite3.connect(db_path, timeout=20)
# enable_wal_mode(conn_script2) # 启用WAL模式
# # ... script2使用 conn_script2 ...
# conn_script2.close()

4. 批量插入数据

如果存在大量数据需要写入,逐行插入会频繁地开启和关闭事务,导致效率低下。将多行数据合并到一个 INSERT 语句中进行批量插入,可以显著减少数据库操作次数和事务开销,从而缩短写入锁定的时间。

MagickPen
MagickPen

在线AI英语写作助手,像魔术师一样在几秒钟内写出任何东西。

下载

批量插入示例:

def db_batch_insert_function(conn, rows):
    lastrowid = None
    try:
        cursor = conn.cursor()
        sql = '''INSERT INTO table1(site_name,payload_timestamp_utc,device_id,start_time_utc,end_time_utc,
        value) VALUES(?,?,?,?,?,?) '''
        cursor.executemany(sql, rows) # 使用 executemany 进行批量插入
        conn.commit()
        lastrowid = cursor.lastrowid # 对于 executemany,lastrowid 通常是最后插入行的id
    except sqlite3.Error as e:
        print(f"批量插入数据时发生错误: {e}")
    return lastrowid

# 示例调用
# data_to_insert = [
#     ('siteA', '2023-01-01 10:00:00', 'dev1', '2023-01-01 09:00:00', '2023-01-01 11:00:00', '100'),
#     ('siteB', '2023-01-01 10:05:00', 'dev2', '2023-01-01 09:30:00', '2023-01-01 11:30:00', '120'),
#     # ... 更多行
# ]
# db_batch_insert_function(conn_script1, data_to_insert)

建议根据实际数据量和写入频率,调整每次批量插入的行数,例如一次插入100到1000行。

最佳实践与注意事项

除了上述性能优化策略,以下编码实践对于构建健壮的SQLite3应用同样重要:

1. 优化时间戳存储方式

SQLite3没有内置的日期时间类型。将时间戳存储为文本(TEXT)虽然可行,但在比较和查询时效率较低,且占用空间较大。最佳实践是将其存储为整数(INTEGER),表示Unix纪元时间(自1970年1月1日UTC以来的秒数或毫秒数)。

修改表结构示例:

CREATE TABLE IF NOT EXISTS table1 (
    [id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    [site_name] TEXT,
    [payload_timestamp_utc] INTEGER, -- 修改为INTEGER
    [device_id] TEXT,
    [start_time_utc] INTEGER,      -- 修改为INTEGER
    [end_time_utc] INTEGER,        -- 修改为INTEGER
    [value] TEXT
);

在插入数据时,可以使用Python的 datetime 模块将时间字符串转换为Unix时间戳,或在SQL查询中使用 strftime('%s', ...) 函数进行转换。读取时,也可以使用 datetime(payload_timestamp_utc, 'unixepoch') 转换回可读格式。

2. 严格使用参数化查询

切勿通过字符串拼接的方式将用户输入或变量值直接插入到SQL查询中。这种做法极易导致SQL注入漏洞。SQLite3(以及大多数数据库驱动)提供了参数化查询机制,可以安全地将变量传递给查询。

原始的读取函数使用了字符串格式化 %s,这存在严重的安全隐患。 修改后的参数化查询示例:

# db_read_function_optimized 中已包含此修改
# temp_df = pd.read_sql(query, conn, params=(param1, param1, param3, param2))

# 对于非pandas的 execute 方法
# cursor.execute("SELECT * FROM table1 WHERE device_id=? AND start_time_utc

通过使用问号占位符(?)并将参数作为元组传递给 execute 或 read_sql 的 params 参数,可以有效防止SQL注入。

3. 规范异常处理

在函数内部捕获并打印异常,然后返回一个默认值(如'null'或None),可能会掩盖问题的真实性质,并使调用者难以判断操作是否成功或失败的原因。

最佳实践是让调用者处理异常: 函数内部只负责执行核心逻辑,当发生错误时,抛出异常。调用者可以根据具体的业务逻辑来决定如何处理这些异常(例如重试、记录日志、回滚事务或向用户显示错误信息)。

优化后的异常处理示例:

# 假设 conn 已经传入
def db_read_function_robust(conn, device_id, timestamp_end, timestamp_start):
    query = '''
        SELECT * FROM table1
        WHERE device_id=?
          AND payload_timestamp_utc=(SELECT MAX(payload_timestamp_utc) FROM table1 WHERE device_id=?)
          AND start_time_utc?
        ORDER BY start_time_utc ASC
    '''
    # 直接执行查询,如果发生错误,则抛出异常
    df = pd.read_sql(query, conn, params=(device_id, device_id, timestamp_end, timestamp_start))
    return df

# 调用方处理异常
# try:
#     result_df = db_read_function_robust(my_conn, 'deviceX', '2023-01-01 12:00:00', '2023-01-01 10:00:00')
#     # 处理 result_df
# except sqlite3.Error as e:
#     print(f"在主程序中捕获到数据库错误: {e}")
#     # 根据错误类型进行进一步处理,例如日志记录、重试或退出

此外,temp_df='null' 的初始化也应改为 temp_df=None,因为 None 是Python中表示“无值”的标准方式。

总结

通过综合应用上述优化策略和最佳实践,可以显著提升Python应用中SQLite3数据库的并发读写性能和整体稳定性。核心在于从数据库层面优化查询效率(索引),改善并发机制(WAL模式),以及合理管理资源(连接复用、批量插入)。同时,采用安全的编码习惯(参数化查询、规范异常处理)和高效的数据存储方式(整数时间戳),将有助于构建更加健壮、可维护的数据库应用。在实施这些优化时,建议进行性能测试,以验证其对特定应用场景的实际效果。

相关文章

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

728

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

328

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

350

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

1263

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

360

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

841

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

581

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

423

2024.04.29

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

1

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 3.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号