参数化查询
防止 SQL 注入
参数化查询 是 CoddyKit 上的免费 Python Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。
字符串拼接的危险
通过拼接字符串来构建 SQL 存在风险。如果用户输入包含 SQL,它就可能改变您的查询。这称为 SQL 注入。
处理不受信任的数据时,绝不要这样做。
name = 'Alice'
# Unsafe pattern - do NOT do this
query = "SELECT * FROM users WHERE name = '" + name + "'"
print(query)注入的表现
设想一个恶意值。将它拼接进去会彻底改变查询的含义。
name = "x' OR '1'='1"
query = "SELECT * FROM users WHERE name = '" + name + "'"
print(query)
print('The OR clause makes every row match!')占位符来解决问题
安全的方式是使用参数化查询。使用 ? 作为占位符,并将值作为元组传递。驱动程序会安全地对这些值进行转义。
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
cur.execute('INSERT INTO users (name) VALUES (?)', ('Alice',))
cur.execute('SELECT * FROM users')
print(cur.fetchall())
conn.close()多个占位符
每个值使用一个 ?。按照它们出现的顺序,将值放入元组中传递。
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)')
cur.execute('INSERT INTO users (name, age) VALUES (?, ?)', ('Bob', 25))
cur.execute('SELECT * FROM users')
print(cur.fetchall())
conn.close()单值元组
单元素元组需要在末尾添加逗号:('Alice',)。如果没有逗号,Python 会将括号仅视为分组符号。
good = ('Alice',)
bad = ('Alice')
print(type(good), type(bad))注入尝试被阻止
使用占位符时,恶意值会被视为普通数据,而不是 SQL。它只会匹配不到任何内容。
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
cur.execute('INSERT INTO users (name) VALUES (?)', ('Alice',))
evil = "x' OR '1'='1"
cur.execute('SELECT * FROM users WHERE name = ?', (evil,))
print('Rows matched:', cur.fetchall())
conn.close()命名占位符
SQLite 还支持使用 :name 语法的命名占位符。请传递一个值字典。
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)')
cur.execute('INSERT INTO users (name, age) VALUES (:n, :a)', {'n': 'Carol', 'a': 40})
cur.execute('SELECT * FROM users')
print(cur.fetchall())
conn.close()WHERE 中的参数
占位符可以用于任何接受值的子句,包括 WHERE。
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)')
cur.execute('INSERT INTO users (name, age) VALUES (?, ?)', ('Alice', 30))
cur.execute('INSERT INTO users (name, age) VALUES (?, ?)', ('Bob', 25))
cur.execute('SELECT name FROM users WHERE age > ?', (28,))
print(cur.fetchall())
conn.close()executemany
要插入多行,请将 executemany() 与元组列表结合使用。这样更快,而且仍然安全。
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
rows = [('Alice',), ('Bob',), ('Carol',)]
cur.executemany('INSERT INTO users (name) VALUES (?)', rows)
cur.execute('SELECT COUNT(*) FROM users')
print(cur.fetchone())
conn.close()占位符不适用于标识符
占位符只适用于值,不适用于表名或列名。这些名称必须来自您自己代码中受信任的白名单。
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
allowed = {'name', 'id'}
col = 'name'
if col in allowed:
cur.execute('SELECT ' + col + ' FROM users')
print('Safe column query ran')
conn.close()可复用的插入函数
将参数化插入操作封装到函数中,可以让代码保持整洁,并始终确保安全。
import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
def add_user(cursor, name):
cursor.execute('INSERT INTO users (name) VALUES (?)', (name,))
add_user(cur, 'Dave')
cur.execute('SELECT * FROM users')
print(cur.fetchall())
conn.close()快速检查
测试您对安全查询的了解。
回顾
您已经学会编写可防范注入的查询。
- 绝不要将不受信任的输入拼接到 SQL 中
- 使用
?占位符和由值组成的元组 - 命名占位符
:name接受字典 executemany()可以安全地插入多行- 占位符用于值,而不是标识符
常见问题解答
「参数化查询」课时是免费的吗?
是的 — 「参数化查询」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。
「参数化查询」这节课中我会学到什么?
防止 SQL 注入 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Python Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「参数化查询」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Python Academy 课中编写并运行代码吗?
能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。