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、大表还是小表

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

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

优势:

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

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

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

劣势:

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

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

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

优势:

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

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

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

劣势:

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

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

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

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

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

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

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

相关推荐
南城花随雪。1 分钟前
硬盘(HDD)与固态硬盘(SSD)详细解读
数据库
儿时可乖了2 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
懒是一种态度4 分钟前
Golang 调用 mongodb 的函数
数据库·mongodb·golang
天海华兮6 分钟前
mysql 去重 补全 取出重复 变量 函数 和存储过程
数据库·mysql
CV学术叫叫兽32 分钟前
一站式学习:害虫识别与分类图像分割
学习·分类·数据挖掘
我们的五年43 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
gma9991 小时前
Etcd 框架
数据库·etcd
爱吃青椒不爱吃西红柿‍️1 小时前
华为ASP与CSP是什么?
服务器·前端·数据库
一棵开花的树,枝芽无限靠近你1 小时前
【PPTist】添加PPT模版
前端·学习·编辑器·html
VertexGeek1 小时前
Rust学习(八):异常处理和宏编程:
学习·算法·rust