ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SQLite3从入门到实战:核心语法、事务索引与Python操作全解析

SQLite3从入门到实战:核心语法、事务索引与Python操作全解析 1. 先搞清楚SQLite3到底是什么SQLite3这个东西我接触了快十年到现在依然觉得它是数据库领域里最被低估的工具之一。先说人话SQLite3就是一个文件型数据库没有独立的服务器进程没有端口没有账号密码整个数据库就是磁盘上的一个文件。你写程序的时候直接通过API去读写这个文件不需要像MySQL或者PostgreSQL那样先启动一个服务再创建用户、授权、连接远程端口。正因为它这么轻几乎所有你叫得上名字的软件里都有它的身影。手机上的通讯录、浏览器里的收藏夹、微信的聊天记录、很多嵌入式设备的配置存储甚至一些大型网站的后端缓存都在用SQLite3。如果你用Python写过东西标准库里的sqlite3模块就是官方内置的不需要额外装任何东西。很多做数据分析的朋友可能平时用的都是Pandas但如果你处理的数据量超过几百万行直接从CSV文件读就会明显变慢这时候把数据塞进SQLite3再查体验完全是两回事。这篇内容我想从头到尾拆一遍SQLite3的语法核心目标有两个第一让你能在半小时内搭建起自己的一套完整操作体系从建表、增删改查到事务、索引、视图和触发器第二把我实际踩过的坑、用过的技巧一并交代出来。适用范围很广还没入门的初学者可以当系统教程看写过几年SQL但没细研究过SQLite3的工程老手也能在这篇文章里找到一些你自己平时没注意过的细节。SQLite3的SQL语法大体上遵循标准SQL但又有很多独属于自己的方言。这套方言一旦掌握了后面换到MySQL、PostgreSQL虽然不能无缝移植但基本概念是相通的。2. 环境准备与SQLite3安装实操2.1 三种最省事的安装方式先解决一个问题怎么把SQLite3弄到本地来用。不同平台我分别说三种最省事的方式。第一种是命令行方式。如果你是Linux或者macOS用户直接在终端里敲# Debian/Ubuntu sudo apt-get install sqlite3 # CentOS/RHEL/Fedora sudo yum install sqlite3 # macOS自带不用装 sqlite3 --versionmacOS其实是系统自带的Windows的话需要去SQLite官网下载预编译的二进制包。注意官网有两类文件一类是命令行工具sqlite-tools-win-x64一类是动态链接库sqlite-dll-win-x64。如果你只是想敲SQL体验一下下载tools就行解压后把sqlite3.exe放到一个你记得住的目录比如D:\software\sqlite然后把这个目录加进Path环境变量。加好之后重新开一个终端敲sqlite3就能进去了。第二种方式用Python调包。我目前干数据分析、写自动化脚本的时候基本都是这条路因为Python自带驱动连配置都省了打开终端敲一行就行import sqlite3 conn sqlite3.connect(demo.db) print(conn)运行完这段如果目录下多出一个demo.db文件说明环境已经通了。connect()这个方法是自动创建文件的哪怕你只是写了一个connect(test.db)然后什么都没干文件也会被创建出来这也是SQLite3的一个特性——零成本起步。第三种方式是图形化工具。Navicat、DBeaver、DB Browser for SQLite这三款我都用过其中DB Browser for SQLite是完全免费的界面也很直观适合完全不习惯命令行的新手。但我得说句实话如果你真想成为SQLite3的实战高手一定要把命令行操作练熟。图形化工具只是方便看数据很难帮你真正建立对SQL的直觉。2.2 进入命令行与查看基本帮助安装完成后终端输入sqlite3进入交互模式界面会显示类似sqlite的提示符。在这个提示符下可以执行任意SQL语句注意每条SQL语句必须以分号;结尾这是新手最容易漏掉的一个地方。敲几条最简单的命令来验证-- 查看版本 sqlite select sqlite_version(); -- 查看帮助 sqlite .help -- 退出 sqlite .quit这里有个容易混淆的点sqlite3的命令行工具内部有两种语言。一种是以英文句点开头的点命令比如.tables、.schema、.quit、.headers on它们不是SQL而是命令行工具自身的功能用于控制显示格式、列举数据库对象等另一种才是纯粹的SQL语句CREATE、SELECT、INSERT、UPDATE、DELETE等等。很多新手会把.tables后面加分号或者把SELECT写在.databases前面然后发现莫名其妙报错。记住一句话点命令不加分号SQL语句必须加分号。3. SQLite3核心语法拆解从建表到增删改查3.1 创建表格类型要选对约束要明白几乎所有数据库操作的第一步都是建表。SQLite3的建表语法整体是这样的CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER DEFAULT 18, created_at TEXT DEFAULT (datetime(now, localtime)) );我逐行解释一下这里面每个关键字的意义。IF NOT EXISTS是幂等保护。如果你重复执行这段建表语句没有这个关键字会直接报错table user already exists加上之后就会静默跳过。自动化脚本里建议每次都写上。id INTEGER PRIMARY KEY AUTOINCREMENT整型主键自增表示每条记录的ID是唯一的插入的时候不指定它会自动按1、2、3……排下去。这里面有一个很多人不知道的底层差异如果只写INTEGER PRIMARY KEY不带AUTOINCREMENT其实也能自增而且性能更好区别在于删除最大ID后会不会复用之前的ID。AUTOINCREMENT会保证ID只增不减但会多耗费一点存储空间。普通的业务表我建议直接用INTEGER PRIMARY KEY就够了只有当你需要绝对不重号、每个ID只用一次的场景才需要加AUTOINCREMENT。name TEXT NOT NULLTEXT是SQLite3的文本类型NOT NULL表示这一列不允许为空。比如注册用户必须填写姓名那就在建表这一层做约束而不是靠业务代码去判断。age INTEGER DEFAULT 18DEFAULT关键字当插入数据时没有提供age值数据库就会自动填18。这个设计很实用可以减少很多应用层的判断代码。created_at TEXT DEFAULT (datetime(now,localtime))这里用的是SQLite3内置的datetime函数取当前本地时间并格式化成YYYY-MM-DD HH:MM:SS的字符串。注意SQLite3其实没有专门的时间类型所有人习惯上都用TEXT存日期时间排序和比较也都能正常工作。这一点跟MySQL差别很大在SQLite里你永远不会看到DATETIME这个类型是独立存储的——它底层就是文本。关于SQLite3的数据类型官方文档里说的是动态类型简单理解就是你声明某一列是INTEGER不代表以后只能往里面放整数实际上放字符串它也不会报错。这种宽松在开发期看似方便但生产中非常容易埋雷。我强烈的建议是在建表时对每一列都声明类型业务代码里再严格校验一遍两边都不能松。3.2 插入数据单条、批量与传统VALUES语法建好表之后最基础的操作就是插入数据。SQLite3的插入语法有三种形态我挨个讲清楚。第一种指定列插入INSERT INTO user (name, email, age) VALUES (张三, zhangsanexample.com, 25);这种情况下建表时设置了DEFAULT的列可以不写让数据库自动填。比如没有指定created_at它会自动取当前时间。第二种全列插入INSERT INTO user VALUES (1, 李四, lisiexample.com, 30, 2024-01-01 12:00:00);这种写法必须把表里每一列的值都按顺序写出来否则就会报错。从可维护性角度看我建议少用全列插入。毕竟一旦表结构调整过比如中间加了一列这种INSERT语句就会全部失效排查起来很痛苦。第三种批量插入也是我实际工作中最常用的INSERT INTO user (name, email, age) VALUES (王五, wangwuexample.com, 22), (赵六, zhaoliuexample.com, 28), (孙七, sunqiexample.com, 35);这个写法在SQLite3的较新版本里是支持的一条语句插几万条记录都没问题。如果你用Python驱动还可以用executemany()传入一个列表性能更高后面的实战章节我会具体演示。3.3 查询数据WHERE、ORDER BY、LIMIT与聚合函数查询是所有SQL操作里最核心也最考验功力的部分。SQLite3的基本查询语法如下SELECT column1, column2 FROM table_name WHERE condition ORDER BY column1 ASC/DESC LIMIT offset, count;可以从下面几个关键点来掌握。WHERE条件里最常用的有这些操作符、!或、、、、、LIKE、IN、BETWEEN、AND、OR。举几个实际例子-- 精确查找 SELECT * FROM user WHERE name 张三; -- 模糊匹配%表示任意长度的任意字符_表示单个字符 SELECT * FROM user WHERE email LIKE %example.com; -- 范围过滤 SELECT * FROM user WHERE age BETWEEN 20 AND 30; -- 集合过滤 SELECT * FROM user WHERE name IN (张三, 李四, 王五);需要提醒一个LIKE相关的细节SQLite3的LIKE匹配在默认配置下是不区分大小写的而且ASCII字符大小写也不敏感。如果你需要精确匹配大小写要用GLOB关键字它是SQLite3特有的语法支持通配符*和?语义上更像Unix shell的匹配规则。ORDER BY用于排序支持多列排序SELECT * FROM user ORDER BY age DESC, created_at ASC;这个排序逻辑是先按age降序排age相同再按created_at升序排。设计表结构时如果某个字段经常用于排序给它加索引会极大提升查询速度索引具体怎么建我放到第4节细讲。LIMIT有两个作用一是限制返回行数二是分页-- 返回前10条 SELECT * FROM user LIMIT 10; -- 分页跳过前20条取10条第3页每页10条 SELECT * FROM user LIMIT 10 OFFSET 20;聚合函数方面SQLite3提供了完整的五个基础函数COUNT计数、SUM求和、AVG平均值、MAX最大值、MIN最小值。它们常常和GROUP BY配合使用SELECT age, COUNT(*) FROM user GROUP BY age; -- 用HAVING对分组后的结果做二次过滤 SELECT age, COUNT(*) FROM user GROUP BY age HAVING COUNT(*) 1;这里的HAVING和WHERE的区别是WHERE是在分组之前过滤原始行HAVING是在分组之后过滤分组。这个顺序搞错了很容易写出一堆逻辑不对的查询。3.4 更新与删除写完条件仔细想三遍UPDATE和DELETE在SQLite3里的语法非常简洁UPDATE user SET age age 1 WHERE name 张三; DELETE FROM user WHERE id 1;两个操作都要特别注意一件事WHERE条件一定不能漏。如果漏了副作用极其严重——UPDATE会把全表每一行都改掉DELETE会把全表清空。我这句话说得很直白因为这是我见过也经历过的翻车现场。现在我在极重要的生产库上执行DELETE之前一定会先用同样的WHERE条件跑一遍SELECT-- 先查出来看看 SELECT * FROM user WHERE name 张三; -- 确认无误后再删 DELETE FROM user WHERE name 张三;在命令行工具里DELETE操作默认是自动提交的一旦执行没有任何恢复机制除非你提前有备份。所以我现在凡是执行批量DELETE都有一个习惯先把数据导出成SQL文件sqlite3 demo.db .dump backup.sql这句话把整个数据库的逻辑备份导出到backup.sql文件里。万一出事重新执行sqlite3 demo.db backup.sql就能恢复。这个习惯值得所有动手实操的人养成。4. SQLite3进阶语法事务、索引、视图与触发器4.1 事务多条语句的后悔药事务是保证数据一致性的关键机制。拿转账举例A账户扣1000元、B账户加1000元这两条UPDATE必须同时成功或同时失败。如果第一条成功第二条失败钱就凭空少了。SQLite3默认是自动提交模式也就是说每条SQL语句执行完立即生效。想要把多条语句合成一个原子操作需要显式使用事务BEGIN TRANSACTION; UPDATE account SET balance balance - 1000 WHERE id 1; UPDATE account SET balance balance 1000 WHERE id 2; COMMIT;中间如果任何一步出了问题执行ROLLBACK;回滚所有操作全部撤销数据恢复到BEGIN之前的状态。关于SQLite3事务有几个细节点需要注意。第一事务开启期间会锁定数据库文件别的连接写入会被阻塞。所以事务应该短平快不要在一个事务里做复杂的网络请求或者长时间计算。第二SQLite3的事务还有几种隔离级别可以通过PRAGMA journal_mode设置比如WAL模式Write-Ahead Logging在大并发下比默认的delete模式好很多读和写可以并行。代码里只要执行一次PRAGMA journal_modeWAL;后续就都是WAL模式了效果是重读不阻塞写、重写不阻塞读。我个人现在所有生产项目都会开WAL。第三如果忘记写COMMIT就关闭了连接未提交的事务会被自动回滚这不一定是坏事但如果你本意是保留数据就亏了。4.2 索引提速的关键但是有代价没有索引的表相当于一本没有目录的书查询时只能从头到尾一页页翻。SQLite3中创建索引的语法CREATE INDEX idx_user_email ON user(email); -- 唯一索引确保列值不重复 CREATE UNIQUE INDEX idx_user_email_unique ON user(email);索引为什么能提速底层结构是B-Tree查找时间复杂度从全表扫描的O(N)降到了索引查找的O(log N)。几万行时可能感觉不明显到了几百万行、几千万行差别就是毫秒和秒的区别。索引也不是越多越好。每建一个索引数据库在插入、更新、删除时都要额外维护索引结构这会让写入变慢。所以基本原则是给经常出现在WHERE和ORDER BY中的列建索引给重复率太低的列建索引意义不大。比如性别列只有男女两个值建索引并不能大幅缩小扫描范围。查看一个表有哪些索引用-- 列出表名和索引名 SELECT * FROM sqlite_master WHERE type index;删掉索引DROP INDEX IF EXISTS idx_user_email;4.3 视图逻辑复用简化复杂查询视图就是一条命名的SELECT语句它不真实存储数据每次查询视图时底层都会去执行那条SELECT。创建视图的语法CREATE VIEW v_user_over_25 AS SELECT name, email, age FROM user WHERE age 25;创建之后你可以像查表一样查视图SELECT * FROM v_user_over_25;视图的好处是可以把复杂的多表JOIN查询封装起来让下游使用的人只面对一张虚拟表。比如后端报表里经常要查用户订单汇总就可以先建一个视图CREATE VIEW v_order_summary AS SELECT u.name, COUNT(o.id) AS order_count, SUM(o.amount) AS total_amount FROM user u LEFT JOIN orders o ON u.id o.user_id GROUP BY u.id;这样每个业务方来查报表一行SQL就够了。视图还有一个限制要清楚视图默认是只读的不能直接对视图执行INSERT、UPDATE、DELETE操作除非你创建了特定的INSTEAD OF触发器这是更高级的玩法。所有对视图的写操作都会报错。4.4 触发器当数据变化时的自动任务触发器是SQLite3里被很多人忽略但实际很有用的功能。它可以在某个表发生INSERT、UPDATE、DELETE时自动执行一段SQL。语法结构CREATE TRIGGER trigger_name AFTER INSERT ON user BEGIN INSERT INTO user_log (action, user_id, operate_time) VALUES (INSERT, NEW.id, datetime(now)); END;这段代码的意思是每当user表插入新行就自动把操作记录写到user_log表。NEW代表新插入的行OLD代表被删除或被替换的旧行。更新操作时NEW和OLD都能用分别表示更新后的值和更新前的值。触发器适合用来做审计日志、同步冗余字段、自动更新时间戳。不过它也有明显的坑排错难度比普通SQL高得多。如果某天你的UPDATE发现数据异常却怎么都查不到代码里的问题很可能就是某个触发器在暗中作祟。查看所有触发器SELECT name FROM sqlite_master WHERE type trigger;删除触发器DROP TRIGGER IF EXISTS trigger_name;谨慎使用最好每个触发器都写上注释说明用途和创建人。否则过几个月你自己都会看不懂。5. 多表JOIN实战与命令行工具技巧5.1 JOIN的三种主要类型真实业务很少只操作一张表。两张表联合查询是常态。SQLite3支持主要的JOIN类型INNER JOIN、LEFT JOIN、CROSS JOIN。简单说一个业务场景用户表和订单表查每个用户的订单信息。先建两张表并插入测试数据CREATE TABLE user ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE orders ( id INTEGER PRIMARY KEY, user_id INTEGER, product TEXT, amount REAL ); INSERT INTO user (id, name) VALUES (1, 张三), (2, 李四), (3, 王五); INSERT INTO orders (id, user_id, product, amount) VALUES (1, 1, 手机, 2999.00), (2, 1, 耳机, 199.00), (3, 2, 键盘, 459.00);内连接只返回两边都匹配的行SELECT user.name, orders.product, orders.amount FROM user INNER JOIN orders ON user.id orders.user_id;结果name|product|amount 张三|手机|2999.0 张三|耳机|199.0 李四|键盘|459.0可以看到王五没有任何订单所以在INNER JOIN的结果里不出现。左连接LEFT JOIN会保留左表的全部行右表没有匹配的部分用NULL填充SELECT user.name, orders.product, orders.amount FROM user LEFT JOIN orders ON user.id orders.user_id;结果里王五会出现一条记录product和amount都是NULL。LEFT JOIN是日常业务里最常用的JOIN类型特别适合统计每个用户有多少订单这种场景配合GROUP BY和COUNT可以很快得出汇总。JOIN查询里最容易犯的错误是关联条件写错导致笛卡尔积爆炸。所谓笛卡尔积就是两张表每一行都相互组合结果行数等于两个表行数的乘积。比如一张表1000行、另一张表1000行不写ON条件直接JOIN会得到100万行。所以每次写JOIN一定要检查ON后面的关联条件是否正确。5.2 命令行工具的隐藏技巧前面说过点命令和SQL语句的区别这里把几个高频命令集中说一下。.headers on先打开列头显示否则查询结果默认不显示列名只显示一堆值。.mode column把输出变成对齐的列模式比默认的竖线分隔好看很多。.width可以设置每列宽度。你在命令行工具里看数据不习惯大概率就是没开这两个设置。查看表结构的命令是.schema 表名。它会显示建表语句非常有用。当你不确定某张表有哪些列、什么类型时直接执行它比翻文档快得多。从CSV文件导入数据到SQLite3也有标准的命令。假设有一个data.csv文件第一行是列名id,name,age内容用逗号分隔.mode csv .import data.csv user这样就把CSV内容导入到user表。注意细节如果表不存在.import会自动建表但列类型会全部变成TEXT。如果表已存在它会按列名匹配导入。导入之前最好先看一眼文件编码UTF-8没问题GBK或者含BOM的可能会出问题需要先把CSV转为UTF-8。导出数据的话SQLite3支持.dump和.output组合.output backup.sql .dump .output stdout这样把整个数据库的建表语句和数据全部导出到backup.sql之后可以用.read backup.sql重新载入。备份恢复这条路我建议每个人都走一遍不用等灾难发生再研究。6. 从SQLite3到Python实战代码走一遍6.1 连接、建表、写入的基本套路SQLite3绝大多数应用场景都不是直接在命令行敲SQL而是写在程序里。Python内置的sqlite3模块是我用得最顺手的先说标准流程。import sqlite3 # 连接数据库不存在会自动创建 conn sqlite3.connect(shop.db) # 创建游标 cur conn.cursor() # 建表 cur.execute( CREATE TABLE IF NOT EXISTS product ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL NOT NULL, stock INTEGER DEFAULT 0 ) ) # 插入单条 cur.execute(INSERT INTO product (name, price, stock) VALUES (?, ?, ?), (机械键盘, 459.0, 100)) # 批量插入 data [ (无线鼠标, 129.0, 200), (显示器, 1299.0, 50), (USB-C扩展坞, 199.0, 80), ] cur.executemany(INSERT INTO product (name, price, stock) VALUES (?, ?, ?), data) # 提交事务 conn.commit() # 查询 cur.execute(SELECT * FROM product WHERE price ?, (200,)) rows cur.fetchall() for row in rows: print(row) # 关闭连接 conn.close()这里最关键的是参数占位符?。我在很多项目代码里看到有人用字符串拼接SQL# 这种做法极其危险 cur.execute(fSELECT * FROM user WHERE name {name})一旦name里包含单引号或者恶意拼接内容轻则SQL报错重则破坏整个数据库结构。SQLite3官方推荐的写法就是用?占位符驱动会帮你做转义。这个习惯要在一开始就养成哪怕只是写个脚本自己用也建议别用字符串拼接。另外注意Python的sqlite3模块默认不会自动提交事务。你必须显式调用conn.commit()否则数据不会真正落盘。我早期在这个问题上栽过跟头代码运行没报错但是数据库文件里就是找不到数据。查了半天才发现连接关闭时事务被回滚了。6.2 查询结果与字典模式默认情况下使用fetchall()拿到的是一个由元组组成的列表每一行是一个元组只能靠下标访问列比如row[0]是idrow[1]是name。当表结构比较简单时问题不大一旦列数变多或者你经常改动表结构这种写法就变得非常不友好。SQLite3的Python模块提供了Row类型和字典模式。通过设置连接的行工厂可以让每行像一个字典一样通过列名访问conn sqlite3.connect(shop.db) conn.row_factory sqlite3.Row cur conn.cursor() cur.execute(SELECT id, name, price FROM product WHERE stock ?, (0,)) rows cur.fetchall() for row in rows: print(row[name], row[price])这样代码的可读性能上一个台阶。尤其是在写复杂的查询逻辑时row[name]比row[1]直观太多了列顺序发生变化也不会导致代码默默取错数据。对于只读场景还可以把连接设置为自动提交省去手动commitconn sqlite3.connect(file:shop.db?modero, uriTrue)file:这种URI写法是SQLite3支持的高级特性modero表示以只读模式打开。如果你的脚本只需要查询不需要写入推荐用这个模式可以有效防止手滑误操作。6.3 事务在Python中的正确姿势在Python里操作事务不能用裸的BEGIN语句。因为Python的sqlite3模块在conn.commit()和conn.rollback()之外还默认隐藏了一些事务行为。比较规范的做法是用with上下文管理器conn sqlite3.connect(shop.db) try: with conn: cur conn.cursor() cur.execute(UPDATE product SET stock stock - ? WHERE id ?, (1, 101)) cur.execute(UPDATE product SET stock stock - ? WHERE id ?, (2, 1)) except sqlite3.Error as e: print(事务执行失败已自动回滚, e)with conn块结束时如果内部没有抛出异常就自动执行commit如果抛出了异常则自动执行rollback。这是我目前最推荐的事务写法比手动BEGIN加COMMIT少操心很多。在使用过程中我还发现连接对象和游标对象的生命周期要清晰。连接负责事务、提交、回滚游标负责执行SQL和获取结果。每次操作都新建游标没问题但连接不要频繁打开和关闭尤其不要在一个循环里反复connect每次都重新打开文件非常浪费性能。正确做法是连接一次在整个程序的入口处创建结束时统一关闭。7. 常见问题排查与实用技巧7.1 SQLITE_BUSY并发写入冲突SQLite3最常见的报错之一是database is locked底层错误码是SQLITE_BUSY。这意味着另一个连接正在写数据库当前连接想写的尝试被拒绝了。SQLite3默认对并发写入的支持比较弱因为它每次写入都会锁住整个数据库文件。排除这个问题有几个思路。优先检查是否有别的进程打开了同一个库文件忘记关闭。然后尝试开启WAL模式它显著改善了读写并发PRAGMA journal_modeWAL;在Python驱动中还可以设置连接等待超时conn sqlite3.connect(shop.db, timeout10)这个timeout表示当数据库被锁时最多等待10秒超过后再抛出超时错误。这个参数默认是5秒按需调整即可。7.2 使用WAL模式后的附加文件开启WAL模式之后数据库目录下会发现多出两个文件shop.db-wal和shop.db-shm。很多人会以为它们是垃圾文件把它删掉这是大忌。WAL模式会把尚未合并到主数据库的写操作临时放在-wal文件里删除它可能导致数据丢失。它们会在连接关闭、checkpoint正常执行后自动清理。备份数据库时也要注意直接复制.db文件不够需要连-wal一起复制或者先执行一次PRAGMA wal_checkpoint;把数据合并到主文件再复制主文件。7.3 数据备份的简易脚本备份SQLite3最稳妥、跨平台的方式是用.dump导出逻辑备份。在Python里可以用以下方式import sqlite3 def backup_sqlite(db_path, backup_path): conn sqlite3.connect(db_path) with open(backup_path, w, encodingutf-8) as f: for line in conn.iterdump(): f.write(%s\n % line) conn.close() backup_sqlite(shop.db, shop_backup.sql)iterdump()会生成重建整个数据库所需的SQL语句包括表结构、索引、视图、触发器和数据。这个备份文件可以完整复原数据库。恢复时只需要在空的数据库中执行里面的SQL即可import sqlite3 conn sqlite3.connect(new_shop.db) with open(shop_backup.sql, r, encodingutf-8) as f: sql_script f.read() conn.executescript(sql_script) conn.commit() conn.close()7.4 性能优化的几个小方向数据库操作卡顿是常见问题性能优化有几个优先级非常高的方向。第一使用索引覆盖查询。如果你查的表很大且查询条件经常落在某一个字段上就给这个字段加索引。索引带来的速度提升通常是指数级的。第二批量提交。在Python里用executemany批量写入比逐条execute快一个量级。如果数据量很大还可以配合每5000条一次commit的频率找到性能和事务原子性的平衡点。第三避免在循环中执行无关查询。例如循环里对每个用户ID查数据库那就要想一想能不能用一条IN查询代替# 不要循环查 for uid in user_ids: cur.execute(SELECT * FROM user WHERE id?, (uid,)) # 改用一条查询 placeholders ,.join([?] * len(user_ids)) cur.execute(fSELECT * FROM user WHERE id IN ({placeholders}), user_ids)第四如果只有插入和查询需求可以临时关闭索引或开启PRAGMA synchronous OFF来换写入速度。注意这只是在批量导入数据时临时用生产环境还是保持默认值更安全。7.5 学习路径建议从零基础到实战高手不用一口气把整份文档背完。我建议的学习路线是先掌握第3章的所有基础操作每天用真实业务场景练一练比如记录支出流水、管理书籍库存之类的小应用。等基础熟了再上第4章的索引和事务。等到你写的查询开始变慢就会发现索引的必要性等你的程序开始并发读写就会理解事务的意义。最后再研究触发器、视图这些锦上添花的功能。自己在本地折腾时完全不用怕弄坏数据库建一个test.db随便造大不了删掉重建。实践多少次都不为过。遇到报错就多看错误信息SQLite3本身是个很成熟很稳定的系统绝大多数异常都能靠仔细阅读提示找到破绽。我在实际项目中用SQLite3写了大量工具包括数据采集清洗、报表生成、文件索引管理甚至还有一个小团队的内部CTF题管理平台。每次遇到SQLite3的问题去查文档都能发现一些之前没注意到的用法。这也是它最有魅力的地方看似简单其实藏着无穷多值得深挖的细节。
返回列表