☰
Python数据库学习心得:SQLite、MySQL、PostgreSQL优缺点
2026/10/11 10:41:20 网站建设 项目流程

前言


先说一个方法论问题:「优缺点」这个说法脱离场景是没有意义的。SQLite 的「不支持高并发写」在桌面笔记应用里根本不是缺点,因为那里就不存在并发写;PostgreSQL 的「功能丰富」在一个只存几十行配置表的小工具里也换不来任何收益。所以本文不做「谁更好」的排名,只讲三者在架构上有什么本质差别,以及这些差别在什么场景下会变成你的问题。


写这类对比时最常见的三个错误说法,先纠正掉:



  1. 「SQLite 是小号的 MySQL」。不对。它们不是「同一类东西的不同规模」,而是两种架构:SQLite 是嵌入式库,你的进程直接读写数据库文件;MySQL / PostgreSQL 是客户端-服务器系统,你的进程通过协议和一个独立的服务端进程通信。这个差别决定了并发模型、部署方式和运维成本的完全不同。

  2. 「MySQL 默认存储引擎是 MyISAM」。这是十年前的说法。MySQL 5.5 起默认存储引擎就是 InnoDB,MySQL 8.4 的官方文档里仍然明确写着「InnoDB:MySQL 8.4 的默认存储引擎」。

  3. 「PostgreSQL 比 MySQL 慢」。这类笼统结论没有意义,性能取决于具体负载、索引设计、硬件和版本。不要相信没有测试条件的对比结论。


下面从架构、并发、SQL 能力、运维、Python 接入五个维度拆开讲。


一、架构定位:这是所有差异的源头




维度SQLiteMySQLPostgreSQL



形态嵌入式库,进程内客户端-服务器客户端-服务器

数据存放一个文件(或内存)服务端管理的目录服务端管理的目录

部署零部署,import即用需安装并启动服务端需安装并启动服务端

网络访问无有有

账号与权限文件系统权限,无内置用户体系有完整的用户/授权体系有完整的用户/授权体系

多用户同一台机器、同一文件系统网络内多机多用户网络内多机多用户

典型场景桌面应用、移动端、测试、单机工具Web 应用、中小型业务系统复杂业务、分析型负载、地理信息等



这张表的第一行解释了其余所有行。嵌入式意味着没有网络往返、没有连接管理、没有服务端进程要运维;代价是没有网络访问、没有内置的账号体系、并发写入受限于文件锁。客户端-服务器意味着反过来的一切。


二、并发模型:最容易被低估的差异


SQLite 的写入是全局串行的。同一时刻只允许一个写事务,写操作期间数据库文件被锁住。开启 WAL(预写日志)模式后,读操作可以和写操作并发,这是 WAL 的主要价值,但它不会让多个写者并行。所以:



  • 适合:单进程应用、读多写少、每台设备一个本地库。

  • 不适合:多个应用服务器同时高频写同一个库文件(比如把 SQLite 文件放在网络共享盘上给多台机器写——这是典型的踩坑方式)。


MySQL 和 PostgreSQL 走的是另一条路:多版本并发控制(MVCC),读写互不阻塞,写与写之间靠行级锁(InnoDB)或更细粒度的锁竞争。既然服务端在处理并发,你的应用就可以横向扩展。


但要注意:数据库层的并发只是第一层。Python 侧的 GIL(全局解释器锁)意味着同一个进程内同一时刻只有一个线程在执行字节码,所以多线程对 I/O 密集的数据库操作有效(等待网络时释放 GIL),对 CPU 密集的处理无效。想用多核跑 CPU 密集任务,得用多进程。这两件事经常被混在一起谈,其实是两个独立层次的问题。


Python 标准库的sqlite3模块还多一层约束:连接对象默认不允许跨线程使用(check_same_thread=True)。真要跨线程,就每个线程各建一个连接。


三、SQL 能力与数据类型




能力SQLiteMySQLPostgreSQL



类型系统动态类型(类型亲和性),列声明是建议不是强制静态类型,有严格的列类型静态类型,类型最丰富

原生 JSONjson函数族JSON/JSONB类型(8.0 起)json/jsonb类型,索引与运算符完善

数组类型无原生数组无原生数组原生数组类型

窗口函数3.25 起支持8.0 起支持长期支持

CTE(公用表表达式)支持8.0 起支持长期支持

修改数据后直接返回行支持RETURNING(SQLite 3.35 起)不支持RETURNING支持RETURNING

全文检索FTS5 扩展内置全文索引tsvector/tsquery,长期支持

改表结构能力有限:改名、加列、删列(3.35 起)等,复杂改动要重建表支持较完整的ALTER TABLE支持较完整的ALTER TABLE

外键约束支持,但默认关闭,需每个连接开启支持支持



几点展开:



  • SQLite 的动态类型是个双刃剑。它的列上写的是「类型亲和性」(type affinity),不是强制约束——INTEGER列里理论上可以塞字符串。这让建表很省事,也意味着数据质量要靠应用层或CHECK约束去保证。

  • PostgreSQL 的RETURNING很实用:INSERT ... RETURNING id能一次拿到新生成的主键,省掉一次回查。MySQL 没有这个能力,只能靠自增主键的LAST_INSERT_ID()(Python 驱动里通常表现为cursor.lastrowid),而且批量插入后这个值不可靠。

  • jsonb是 PostgreSQL 的强项:可以建索引、可以用运算符查询内部字段。MySQL 8.0 也有JSON类型,功能在持续补齐。

  • SQLite 的外键默认关闭,且是每连接开关。这一条在「从 SQLite 迁到 PostgreSQL」时最容易出问题——在 SQLite 上跑得好好的删除逻辑,到了强制外键的 PostgreSQL 上直接报错。


四、Python 侧怎么接




数据库驱动说明



SQLitesqlite3(标准库,import即用)无需安装;随 Python 分发

MySQLPyMySQL(纯 Python)、mysql-connector-python(官方)、mysqlclient(C 扩展)都要pip install,参数风格多为%s

PostgreSQLpsycopg(3.x 系列)、psycopg2都要pip install,参数风格为%s



三者的驱动都实现DB-API 2.0(PEP 249),所以接口形态基本一致:connect()→cursor()→execute(sql, params)→fetch*()→commit()/rollback()→close()。差异在细节:




差异点表现



占位符风格SQLite 用?或:name(不支持数字占位符);MySQL / PostgreSQL 驱动用%s

数据库参数名MySQL 驱动常用database=;PostgreSQL 风格驱动常用dbname=

自动提交SQLite 与各 MySQL 驱动默认不自动提交;PostgreSQL 驱动默认不自动提交

事务控制SQLite 有with con:但不关连接;MySQL 驱动也有类似语义



因此「换库」最麻烦的从来不是换驱动,而是 SQL 方言和事务语义。参数占位符从?换成%s是一行改动,但INSERT OR REPLACE、AUTOINCREMENT、LIMIT ? OFFSET ?这些写法各有各的方言,迁移时要逐个核对。


五、同一件事,三种写法


下面这段在三种库上都能跑(SQLite 部分可直接复制执行,因为它不需要任何外部依赖)。观察点是:流程完全一样,只有连接参数和占位符风格在变。


# 适用于 Python 3.8+(这一段用标准库 sqlite3,无需安装任何东西)
import sqlite3

conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
try:
with conn.cursor() as cur:
cur.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT)")
cur.execute("INSERT INTO t(name) VALUES (?)", ("示例",))
cur.execute("SELECT id, name FROM t WHERE name = ?", ("示例",))
row = cur.fetchone()
print(row["id"], row["name"])
conn.commit()
finally:
conn.close()

预期输出
1 示例

把这段代码换到另外两个库上,需要改的只有这几处:




需要改的地方SQLiteMySQL(PyMySQL)PostgreSQL(psycopg 2)



导入import sqlite3import pymysqlimport psycopg2

建连接connect(":memory:")connect(host=, user=, password=, database=, charset="utf8mb4")connect(host=, user=, password=, dbname=)

占位符?%s%s

行工厂conn.row_factory = sqlite3.Rowcursorclass=pymysql.cursors.DictCursorcursor_factory=psycopg2.extras.RealDictCursor

自增主键INTEGER PRIMARY KEYINT AUTO_INCREMENTSERIAL或GENERATED AS IDENTITY

上下文管理器是否关连接否否否(psycopg 3 会关)



这张表就是「DB-API 统一了什么、没统一什么」的实证:统一的是流程和异常层次,没统一的是连接参数名、占位符、行工厂钩子和 DDL 方言。


六、按场景选型




场景建议理由



桌面/单机小工具、配置文件式存储SQLite零部署,一个文件带走

移动端 App 本地存储SQLite无服务端,省电省资源

单元测试、CI 里的临时库SQLite(:memory:)快、干净、无需外部依赖

中小型 Web 应用,读多写少MySQL 或 PostgreSQL有服务端,支持多用户与并发

复杂查询、JSON/数组/地理数据PostgreSQL类型和索引能力更强

团队已有 MySQL 生态与运维经验MySQL迁移成本往往比理论优势更重要

多台服务器同时高频写同一个库必须用客户端-服务器方案SQLite 的写是全局串行的



一条经验:「先用 SQLite,等它真的成为瓶颈再换」通常比「一开始就上服务端」更划算,前提是你把数据访问层封好,切换时不至于满地改 SQL。反之,如果一开始就知道会出现多机并发写、需要账号权限体系、需要网络访问,那就直接上 MySQL 或 PostgreSQL。


常见坑点


1. 把 SQLite 当「小号 MySQL」用


❌ 把 SQLite 库文件放在网络共享盘上,让多台应用服务器同时写。


✅ SQLite 的写是全局串行的,且依赖本地文件锁;需要多机并发写就换成客户端-服务器方案。


2. 用「MySQL 默认是 MyISAM」的知识写新代码


❌ 建表时不指定引擎,又基于「MyISAM 不支持事务」的印象去写代码——实际默认是 InnoDB,事务是生效的。


✅ 现代 MySQL(5.5 起)默认 InnoDB,支持事务、行级锁和崩溃恢复。要显式指定就写ENGINE=InnoDB。


3. 迁移到 PostgreSQL 后外键开始报错


❌ 在 SQLite 上从来没开过PRAGMA foreign_keys=ON,删数据很随意;迁到 PostgreSQL 后被强制外键约束拦下。


✅ 迁移前先做一次数据一致性核查,把 SQLite 侧的孤儿数据清掉;在 SQLite 上也每个连接都开启外键,让行为提前对齐。


4. 指望用RETURNING拿 MySQL 的新主键


❌cur.execute("INSERT ... RETURNING id")在 MySQL 上直接是语法错误。


✅ MySQL 用自增主键 +cur.lastrowid;批量插入后用lastrowid不可靠,需要新主键就逐条插入或用业务唯一键回查。


5. 以为换了驱动就完成了「换库」


❌ 只把import pymysql改成import psycopg,SQL 里?占位符、INSERT OR REPLACE、AUTOINCREMENT通通没改。


✅ 迁移清单要包含:占位符风格、自增列写法、冲突处理语法、分页语法、日期函数、字符串函数。换驱动只是第一行。


6. 把「多线程」当成「解决数据库并发」的万能药


❌ 用threading开 20 个线程去跑 CPU 密集的数据处理,期望加速——受 GIL 限制,同一进程内同一时刻只有一个线程执行字节码。


✅ CPU 密集用multiprocessing;I/O 密集(等数据库响应)多线程才有效。数据库服务端的并发是另一层问题,两件事分开看。


7. 从 SQLite 复制一个库文件当备份


❌ 直接copy一个正在被写入的.db(尤其是开了 WAL 之后),可能拿到不一致的快照。


✅ 用sqlite3的Connection.backup()做在线备份,或先断开所有连接再整体复制。


8. 相信没有测试条件的性能对比结论


❌ 照着某篇「某某数据库快 3 倍」的文章做选型。


✅ 用你自己真实的数据量、查询模式和硬件做基准测试;只拿可复现的测试结果做决策。


总结




维度SQLiteMySQLPostgreSQL



架构嵌入式客户端-服务器客户端-服务器

并发单写者,WAL 下读写可并发MVCC + 行级锁MVCC,锁粒度更细

类型系统动态(类型亲和性)静态静态,类型丰富(数组、JSONB、范围)

运维成本几乎为零中等中等偏高,可调项更多

最适合单机、桌面、测试Web 应用、成熟生态复杂查询、分析、扩展需求



选型的心态应该是:先按架构差别排除不可能的选项,再在剩下的选项里比细节。SQLite 和另外两个不是一个量级上的同类产品,把它们并列比较本身就是一种误导;而 MySQL 与 PostgreSQL 的取舍,多半取决于团队手里的运维经验和既有生态,而不是纸面功能表上的几行差异。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询