SQLite的入门级项目学习记录(二)

再补充一些基础知识:

并行操作的问题

1、可以多游标同时运行

SQLite,对于同一个连接sqlite3.connect(db_file),可以同时创建多个游标,每个游标都是独立的,可以执行各自的SQL命令序列。

python 复制代码
import sqlite3

# 创建数据库连接
conn = sqlite3.connect('example.db')

# 创建第一个游标
cursor1 = conn.cursor()
cursor1.execute("SELECT * FROM table_1")

# 创建第二个游标
cursor2 = conn.cursor()
cursor2.execute("SELECT * FROM table_2")

# 获取第一个游标的结果
results1 = cursor1.fetchall()
for row in results1:
    print(row)

# 获取第二个游标的结果
results2 = cursor2.fetchall()
for row in results2:
    print(row)

# 关闭游标和连接
cursor1.close()
cursor2.close()
conn.close()

在这个例子中,cursor1和cursor2是两个独立的游标,它们可以同时执行不同的查询任务。但是,所有的游标操作都会通过同一个数据库连接conn来与数据库进行交互。

需要注意的是,尽管可以创建多个游标,但SQLite数据库是单线程的,这意味着在任何给定的时间点上,只有一个游标的操作可以被执行。如果需要并发执行多个数据库操作,可能需要考虑使用其他的数据库系统或者使用多线程/多进程的方式来处理。

2、关于多游标多线程访问同一个数据库

SQLite 支持多线程访问数据库,但是它并不是为高并发设计的。

SQLite 使用文件锁来同步对数据库文件的访问,这意味着多个线程可以打开数据库连接并对数据库进行读操作,但是在任何给定时间点上,只允许一个线程进行写操作

在多线程环境中使用 SQLite 时,应该注意以下几点:

读写锁 :SQLite 使用读写锁(Read-Write Lock)来控制对数据库的并发访问。多个线程可以同时获得读锁,但是当有线程持有写锁时,其他线程无法获得读锁或写锁。
事务 :在多线程环境中,每个线程应该使用自己的数据库连接,并且在进行写操作时应该使用事务来确保数据的一致性。
连接池 :由于 SQLite 的并发性能有限,使用连接池可以提高性能,因为连接可以被重用,而不是为每个线程创建一个新的连接。
线程安全 :确保你的应用程序代码是线程安全的,特别是在多线程环境下共享资源时。
性能考虑:由于 SQLite 的设计限制,它在高并发写操作的场景下可能表现不佳。如果你的应用程序需要处理大量的并发写操作,可能需要考虑使用更适合高并发的数据库系统,如 PostgreSQL 或 MySQL。

python 复制代码
import sqlite3
import threading


# 定义工作函数
def worker(conn):
    cursor = conn.cursor()
    cursor.execute("INSERT INTO some_table (column1, column2) VALUES (?, ?)", ('value1', 'value2'))
    conn.commit()
    cursor.close()


# 创建数据库连接
conn = sqlite3.connect('example.db', check_same_thread=False)

# 创建表
conn.execute("CREATE TABLE IF NOT EXISTS some_table (column1 TEXT, column2 TEXT)")

# 创建多个线程
threads = []
for i in range(10):
    t = threading.Thread(target=worker, args=(conn,))
    threads.append(t)
    t.start()

# 等待所有线程完成
for t in threads:
    t.join()

# 关闭连接
conn.close()

3、关于SQLite 的锁机制

SQLite 使用文件锁来处理多个线程同时对同一条记录执行写操作的情况。当一个线程尝试对一条记录执行写操作时,SQLite 会对这条记录加写锁。如果此时有其他线程已经持有该记录的写锁或者正在等待写锁,那么尝试获取写锁的线程将会被阻塞,直到前面的写操作完成并且锁被释放。
SQLite 的锁机制如下
共享锁 (Shared Locks):允许多个线程同时读取同一条记录,但不允许写入。
互斥锁 (Reserved Locks):表示一个线程打算写入一条记录,但尚未开始写入。在这个状态下,其他线程仍然可以获得共享锁进行读取,但不能获得写锁。
未决锁 (Pending Locks):当一个线程试图获取写锁,但发现已经有其他线程持有共享锁或保留锁时,它会进入未决状态。在这个状态下,线程会等待直到可以获取写锁。
独占锁 (Exclusive Locks):当一个线程获得写锁时,其他线程无法获得任何类型的锁,直到写锁被释放。

如果多个线程同时尝试写入同一条记录,SQLite 会按照它们尝试获取锁的顺序来处理。一旦一个线程获得了写锁,其他线程就必须等待,直到写锁被释放。这确保了即使在多线程环境下,SQLite 也能保持数据的一致性和完整性。

然而,由于 SQLite 的这种锁机制,它在高并发写操作的场景下可能表现不佳。如果应用程序需要处理大量的并发写操作,可能需要考虑使用更适合高并发的数据库系统,如 PostgreSQL 或 MySQL。

4、大表还是小表

从速度和效率的角度出发,同一个数据库文件,同样的数据量,是少数几个较大的表还是很多个较小的表有优势?

下面是一些考量因素:
少数几个较大的表

优势:

简化管理:较少的表意味着数据库结构更加简洁,管理起来也更方便。

减少联接复杂度:如果相关的数据都在同一张表中,查询时不需要频繁地联接多个表,从而可以提高查询效率。

事务开销较低:在执行事务时,涉及到的表较少,可以减少事务的复杂性和开销。

劣势:

索引和性能:较大的表通常需要更复杂的索引和优化策略,否则可能导致性能瓶颈。

数据操作:当一个表的记录数量很大时,操作(如插入、更新、删除)可能会变得缓慢,特别是在没有适当索引的情况下。

锁竞争:对大表的并发写操作可能会导致较高的锁竞争,影响性能。
很多较小的表

优势:

模块化:将数据分散到多个表中,可以根据不同的需求对每个表进行优化,增加灵活性。

减少锁竞争:较小的表在进行并发操作时通常会减少锁竞争,提升并发性能。

易于维护:较小的表结构可能更易于维护和调整,例如添加或删除列。

劣势:

联接开销:多个表之间的联接操作可能会增加查询的复杂性和开销,尤其是在联接条件复杂时。

管理复杂性:更多的表意味着需要管理更多的表结构和索引,可能增加维护的复杂性。

事务管理:跨多个表的事务可能更复杂,特别是在涉及到多个表的并发操作时。
选择建议

查询频率:如果你的查询主要集中在某些特定的数据集上,可能会受益于将相关数据集中到少数几个大表中,从而减少联接的复杂性。

数据访问模式:如果你有不同的数据访问模式或业务需求,分散到多个较小的表可以提高灵活性和并发性能。

性能测试:在做出决定之前,建议进行性能测试,看看在你的具体应用场景下,哪种结构更符合需求。可以模拟实际的负载和查询模式来评估性能。

总的来说,没有一种"一刀切"的答案。最佳的表设计应根据具体的应用场景、数据访问模式以及性能需求来决定。如果可能,进行性能测试和评估通常是最好的方法。

相关推荐
夏木~31 分钟前
Oracle 中什么情况下 可以使用 EXISTS 替代 IN 提高查询效率
数据库·oracle
W215534 分钟前
Liunx下MySQL:表的约束
数据库·mysql
黄名富39 分钟前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
言、雲43 分钟前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
OopspoO1 小时前
qcow2镜像大小压缩
学习·性能优化
A懿轩A1 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
居居飒1 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
一个程序员_zhangzhen1 小时前
sqlserver新建用户并分配对视图的只读权限
数据库·sqlserver
zfj3212 小时前
学技术学英文:代码中的锁:悲观锁和乐观锁
数据库·乐观锁··悲观锁·竞态条件
吴冰_hogan2 小时前
MySQL InnoDB 存储引擎 Redo Log(重做日志)详解
数据库·oracle