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

再补充一些基础知识:

并行操作的问题

    1、可以多游标同时运行

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

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。

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

        从速度和效率的角度出发,同一个数据库文件,同样的数据量,是少数几个较大的表还是很多个较小的表有优势?
下面是一些考量因素:
少数几个较大的表
优势:
简化管理:较少的表意味着数据库结构更加简洁,管理起来也更方便。
减少联接复杂度:如果相关的数据都在同一张表中,查询时不需要频繁地联接多个表,从而可以提高查询效率。
事务开销较低:在执行事务时,涉及到的表较少,可以减少事务的复杂性和开销。
劣势:
索引和性能:较大的表通常需要更复杂的索引和优化策略,否则可能导致性能瓶颈。
数据操作:当一个表的记录数量很大时,操作(如插入、更新、删除)可能会变得缓慢,特别是在没有适当索引的情况下。
锁竞争:对大表的并发写操作可能会导致较高的锁竞争,影响性能。
很多较小的表
优势:
模块化:将数据分散到多个表中,可以根据不同的需求对每个表进行优化,增加灵活性。
减少锁竞争:较小的表在进行并发操作时通常会减少锁竞争,提升并发性能。
易于维护:较小的表结构可能更易于维护和调整,例如添加或删除列。
劣势:
联接开销:多个表之间的联接操作可能会增加查询的复杂性和开销,尤其是在联接条件复杂时。
管理复杂性:更多的表意味着需要管理更多的表结构和索引,可能增加维护的复杂性。
事务管理:跨多个表的事务可能更复杂,特别是在涉及到多个表的并发操作时。
选择建议
查询频率:如果你的查询主要集中在某些特定的数据集上,可能会受益于将相关数据集中到少数几个大表中,从而减少联接的复杂性。
数据访问模式:如果你有不同的数据访问模式或业务需求,分散到多个较小的表可以提高灵活性和并发性能。
性能测试:在做出决定之前,建议进行性能测试,看看在你的具体应用场景下,哪种结构更符合需求。可以模拟实际的负载和查询模式来评估性能。
        总的来说,没有一种“一刀切”的答案。最佳的表设计应根据具体的应用场景、数据访问模式以及性能需求来决定。如果可能,进行性能测试和评估通常是最好的方法。

你可能感兴趣的:(SQLite学习笔记,sqlite,学习,数据库)