Appearance
32|SQL 增删改查与参数安全
表建好后,需要对数据进行增删改查(CRUD):插入新记录、查询数据、更新字段、删除记录。同时必须防范 SQL 注入——把用户输入直接拼进 SQL 字符串是最常见的安全漏洞之一。
一、插入(INSERT)
python
cursor.execute(
"INSERT INTO servers (hostname, ip, status) VALUES (?, ?, ?)",
("web-01", "192.168.1.10", "running"),
)
conn.commit()
print(cursor.lastrowid) # 刚插入这条的自增 ID参数用 ? 占位,值放在元组里传。绝对不要用 f-string 或字符串拼接拼 SQL:
python
# 危险:SQL 注入
title = "'; DROP TABLE servers; --"
cursor.execute(f"INSERT INTO servers (hostname) VALUES ('{title}')")
# 实际执行的 SQL:INSERT INTO servers (hostname) VALUES (''; DROP TABLE servers; --')? 占位符是防 SQL 注入的:用户传入的值只会被当成数据,不会被当成 SQL 代码执行。
批量插入
python
servers = [
("web-01", "192.168.1.10", "running"),
("web-02", "192.168.1.11", "stopped"),
("web-03", "192.168.1.12", "running"),
]
cursor.executemany(
"INSERT INTO servers (hostname, ip, status) VALUES (?, ?, ?)",
servers,
)
conn.commit()executemany 比循环调用 execute 效率更高。
二、查询(SELECT)
查询全部
python
cursor.execute("SELECT id, hostname, ip, status FROM servers")
rows = cursor.fetchall()
for row in rows:
print(row) # (1, 'web-01', '192.168.1.10', 'running')条件查询
python
cursor.execute(
"SELECT * FROM servers WHERE status = ?",
("running",),
)
rows = cursor.fetchall()条件值同样用 ? 占位。
查询单条
python
cursor.execute(
"SELECT * FROM servers WHERE id = ?",
(1,),
)
row = cursor.fetchone() # 没有结果时返回 None
if row:
print(row["hostname"])模糊查询
python
cursor.execute(
"SELECT * FROM servers WHERE hostname LIKE ?",
("web-%",),
)% 是通配符,匹配任意字符序列。web-% 匹配 web-01、web-02 等。
排序和限制
python
# 按创建时间倒序,取前 10 条
cursor.execute(
"SELECT * FROM servers ORDER BY created_at DESC LIMIT ?",
(10,),
)| 子句 | 作用 |
|---|---|
ORDER BY 列 ASC | 升序排列 |
ORDER BY 列 DESC | 降序排列 |
LIMIT n | 最多返回 n 条 |
LIMIT n OFFSET m | 跳过 m 条,返回 n 条 |
三、更新(UPDATE)
python
cursor.execute(
"UPDATE servers SET status = ? WHERE id = ?",
("stopped", 1),
)
conn.commit()
print(cursor.rowcount) # 受影响的行数UPDATE 必须带 WHERE 条件,否则更新整张表:
python
# 危险:所有记录的状态都变成 stopped
cursor.execute("UPDATE servers SET status = 'stopped'")四、删除(DELETE)
python
cursor.execute(
"DELETE FROM servers WHERE id = ?",
(1,),
)
conn.commit()DELETE 也必须带 WHERE 条件,否则清空整张表。
五、参数化查询的本质
python
# 参数化查询的执行过程
cursor.execute(
"SELECT * FROM servers WHERE hostname = ?",
("web-01",),
)数据库驱动会把 SQL 模板和参数分开处理:
- 解析 SQL 模板,生成执行计划
- 把参数按类型绑定到占位符
- 执行计划不变,参数值不会被解析为 SQL 代码
这和字符串拼接的本质区别:拼接后的字符串是一个完整的 SQL 语句,数据库会整体解析;参数化查询中参数只是数据填充。
六、常见错误
参数类型不匹配
python
# 错误:传了字符串但字段是整数类型
cursor.execute("SELECT * FROM servers WHERE id = ?", ("1",))
# SQLite 会自动转换,但其他数据库可能报错
# 正确
cursor.execute("SELECT * FROM servers WHERE id = ?", (1,))忘记 commit
python
# 错误:insert/update/delete 后没有 commit,数据不会保存
cursor.execute("INSERT INTO servers ...", (...))
# 缺少 conn.commit()
# 正确
cursor.execute("INSERT INTO servers ...", (...))
conn.commit()SELECT 后调用 commit
python
# 多余:纯查询不需要 commit
cursor.execute("SELECT * FROM servers")
rows = cursor.fetchall()
conn.commit() # 无害但多余executemany 参数格式错误
python
# 错误:参数不是列表的列表/元组的元组
cursor.executemany("INSERT ... VALUES (?)", ["a", "b", "c"])
# 正确:每个参数必须是元组/列表
cursor.executemany("INSERT ... VALUES (?)", [("a",), ("b",), ("c",)])