尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python3 变量写入 SQL 语句的 4 种方法:从拼接、参数化到元组传参
1. 为什么把变量塞进 SQL 字符串会出事从一次登录接口的报错说起先还原一个特别常见的场景。你写了个注册接口前端把username和password传过来你想把这两条数据写进数据库。第一反应大概率是这样name alice password 123456 sql insert into userinfo values( name , password ) cursor.execute(sql)单看功能它确实能跑通数据也进去了。问题出在当name不是普通字符串的时候。假设有人在前端输入框里填了这么一段alice); drop table userinfo; --拼出来的 SQL 就变成了insert into userinfo values(alice); drop table userinfo; --,123456)分号把语句截断第二条drop table直接执行后面的内容被--注释掉。你的用户表就这么没了。这不是危言耸听SQL 注入常年排在各类安全风险榜单的前列根源就是「把用户输入当成 SQL 代码的一部分来拼接」。那正确的做法是什么核心思路只有一句话让 SQL 语句的结构和变量的值彻底分开。SQL 语句里只留占位符变量的值通过参数单独传给数据库驱动由驱动负责转义和绑定。这样无论用户输入什么数据库都只把它当成一个「值」而不是「代码」。Python3 里操作数据库主流驱动有sqlite3标准库自带、pymysql、mysql-connector-python、psycopg2等。它们对参数绑定的支持略有差异但整体套路一致。下面我会把 4 种写法从最危险到最安全依次讲清楚每种都给可直接复制的代码并用恶意输入实测注入是否被拦截。这篇文章适合谁刚学 Python 操作数据库、被「变量怎么加进 SQL」卡住的新手也适合写了几年代码但一直用字符串拼接、想系统梳理参数化写法的开发者。读完你能明确知道哪种写法该用、哪种必须扔进垃圾桶以及遇到TypeError、ProgrammingError这类报错时怎么排查。需要说明的是本文示例以sqlite3和pymysql为主因为这两个最容易在本地复现。如果你用的是其他数据库占位符符号会变比如 PostgreSQL 用%sOracle 用:name但「结构归结构、值归值」的原则完全一样。2. 动手前的准备TaoToken 接入与 sqlite3/pymysql 环境配置在写代码之前先把两件事准备好一个是数据库驱动环境另一个是如果你打算用大模型辅助生成或审查 SQL 代码可以顺手把 TaoToken 的接入配好。先说数据库环境这部分是硬性依赖。2.1 sqlite3 零配置起步sqlite3是 Python 标准库自带的不需要pip install直接 import 就能用。它把整个数据库存成一个文件特别适合本地练习和验证注入问题。建库建表import sqlite3 conn sqlite3.connect(test.db) cursor conn.cursor() cursor.execute( create table if not exists userinfo ( id integer primary key autoincrement, username text not null, password text not null ) ) conn.commit() conn.close()跑完这段当前目录下会多一个test.db文件表结构就建好了。后面所有 sqlite3 的示例都基于这张表。2.2 pymysql 连接 MySQL如果你用的是 MySQL先装驱动pip install pymysql然后建立连接。注意pymysql的占位符是%s不是?这点和 sqlite3 不一样后面会反复强调import pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, passwordyour_password, databasetestdb, charsetutf8mb4 ) cursor conn.cursor()charsetutf8mb4建议加上否则中文和 emoji 可能存进去变问号。2.3 用 TaoToken 辅助生成和审查 SQL 代码写参数化 SQL 的时候占位符符号、参数顺序、元组和字典的对应关系容易搞混。我习惯把表结构和需求丢给模型让它先出一版参数化写法再自己核对。TaoToken 的模型对话入口可以直接用模型对话https://taotoken.net/api/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果你要长期写数据库相关的代码甚至想让 Agent 帮你批量改老项目里的拼接 SQL可以看下 Coding PlanCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewriteAPI Key 在控制台里生成接入文档里有各语言的调用示例API Keyshttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite基础 API 地址是https://taotoken.net/api这个不带 UTM配置客户端时填这个。如果你用的是 Claude Code 这类编码工具Anthropic 兼容入口在这里ClaudeCodeAnthropichttps://taotoken.net/api/anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite环境准备好之后我们进入正题。下面 4 种方法我会按「危险程度」和「推荐程度」排开你可以对照自己的代码看看属于哪一种。3. 4 种变量写入 SQL 的写法从字符串拼接到命名参数的可复制配置这一节是全文的核心。我会把 4 种写法逐一拆开每种都给完整可运行的代码并说明它的适用场景和坑。3.1 方法一字符串拼接f-string / % / 这是最直观、也最危险的写法。三种变体# 变体 A加号拼接 sql insert into userinfo (username, password) values( name , password ) # 变体 B% 格式化 sql insert into userinfo (username, password) values(%s,%s) % (name, password) # 变体 Cf-string sql finsert into userinfo (username, password) values({name},{password})三种写法本质一样变量在 Python 层面就被塞进了 SQL 字符串等execute拿到手时SQL 结构已经被污染了。它们唯一的「优点」是写起来顺手但代价是安全性和可维护性双输。只要变量来自用户输入、外部接口、文件读取就绝对不能这么写。3.2 方法二问号占位符 元组传参sqlite3 推荐sqlite3 用?作为占位符参数以元组形式传给execute的第二个参数import sqlite3 conn sqlite3.connect(test.db) cursor conn.cursor() name alice password 123456 sql insert into userinfo (username, password) values(?, ?) cursor.execute(sql, (name, password)) conn.commit()注意(name, password)这个元组顺序必须和?一一对应。单个参数时也要写成(name,)那个逗号不能省否则 Python 会把它当成普通括号而不是元组这是新手最常踩的坑之一。查询同理sql select id, username from userinfo where username ? cursor.execute(sql, (name,)) rows cursor.fetchall() for row in rows: print(row)3.3 方法三%s 占位符 元组传参pymysql 推荐MySQL 的驱动普遍用%s注意这里虽然长得像 Python 的格式化符号但它是驱动自己的占位符不要在 Python 层面做%运算import pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, passwordyour_password, databasetestdb, charsetutf8mb4 ) cursor conn.cursor() name alice password 123456 sql insert into userinfo (username, password) values(%s, %s) cursor.execute(sql, (name, password)) conn.commit()批量插入时用executemany参数是「元组的列表」data [(bob, 111), (carol, 222), (dave, 333)] sql insert into userinfo (username, password) values(%s, %s) cursor.executemany(sql, data) conn.commit()executemany比循环单条execute快很多因为它减少了往返次数。3.4 方法四命名参数字典传参当参数多、顺序容易记混时命名参数可读性最好。sqlite3 用:namepymysql 用%(name)s# sqlite3 命名参数 sql insert into userinfo (username, password) values(:username, :password) cursor.execute(sql, {username: name, password: password}) # pymysql 命名参数 sql insert into userinfo (username, password) values(%(username)s, %(password)s) cursor.execute(sql, {username: name, password: password})命名参数的好处是顺序无关字典的 key 和占位符名字对应即可。参数一多这种写法比元组清晰得多。3.5 四种写法对照表写法占位符传参类型注入风险可读性推荐度字符串拼接无无极高差禁用问号占位符?元组无中sqlite3 首选%s 占位符%s元组无中pymysql 首选命名参数:name/%(name)s字典无高参数多时首选注意占位符符号由驱动决定不是由数据库决定。同一个 MySQL用 pymysql 是%s用 mysql-connector 也是%s但用某些 ORM 又是另一套。写之前先确认驱动文档。3.6 一个容易忽略的点表名和字段名不能参数化参数化只能用于「值」不能用于表名、字段名、order by后面的列名。下面这种写法是错的# 错误表名不能用占位符 sql select * from ? where id ? cursor.execute(sql, (userinfo, 1)) # 会报错如果表名或列名确实需要动态只能用白名单校验allowed_tables {userinfo, orders, products} table userinfo if table not in allowed_tables: raise ValueError(非法表名) sql fselect * from {table} where id %s cursor.execute(sql, (1,))这里虽然用了 f-string但table已经过白名单校验不是用户可控的任意字符串所以是安全的。这个边界要分清楚。4. 验证请求与成功结果用恶意输入实测注入是否被拦截光说安全不够得实测。这一节我用真实的恶意输入跑一遍看参数化到底拦不拦得住。4.1 构造恶意输入准备一个典型的注入 payloadevil_name alice); drop table userinfo; -- evil_password whatever4.2 用字符串拼接跑一遍危险演示import sqlite3 conn sqlite3.connect(test.db) cursor conn.cursor() evil_name alice); drop table userinfo; -- evil_password whatever sql insert into userinfo (username, password) values( evil_name , evil_password ) print(拼接后的 SQL, sql) try: cursor.execute(sql) conn.commit() except Exception as e: print(执行异常, e)打印出来的 SQL 是insert into userinfo (username, password) values(alice); drop table userinfo; --,whatever)sqlite3 的execute默认一次只执行一条语句所以这里会抛Warning: You can only execute one statement at a time。但换成某些允许堆叠查询的驱动或配置drop table就真的执行了。即便没执行成功这条 SQL 的结构已经被破坏说明拼接本身就是错的。4.3 用参数化跑同样的输入import sqlite3 conn sqlite3.connect(test.db) cursor conn.cursor() evil_name alice); drop table userinfo; -- evil_password whatever sql insert into userinfo (username, password) values(?, ?) cursor.execute(sql, (evil_name, evil_password)) conn.commit() # 查回来看看存进去的是什么 cursor.execute(select username, password from userinfo where password ?, (evil_password,)) print(cursor.fetchall())输出[(alice); drop table userinfo; --, whatever)]整段恶意字符串被当成一个普通的username值原样存进去了drop table没有执行表还在。这就是参数化的效果数据库把?位置的内容当成纯数据不做任何语法解析。4.4 再验证一次表是否还在cursor.execute(select name from sqlite_master where typetable and nameuserinfo) print(cursor.fetchall())输出[(userinfo,)]表完好。如果换成拼接写法且驱动允许堆叠查询这里就会返回空列表。4.5 pymysql 下的同样验证import pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, passwordyour_password, databasetestdb, charsetutf8mb4 ) cursor conn.cursor() evil_name alice); drop table userinfo; -- sql insert into userinfo (username, password) values(%s, %s) cursor.execute(sql, (evil_name, whatever)) conn.commit() cursor.execute(select username from userinfo where password %s, (whatever,)) print(cursor.fetchall())结果一样恶意字符串被完整存为用户名注入无效。提示验证注入时建议在本地或测试库上做别拿生产库练手。虽然参数化能拦住但拼接写法一旦跑在生产上后果不可逆。5. 本篇常见报错排查401、local proxy failed、reading choices 与 OAuth 对照写参数化 SQL 和接入模型辅助时会遇到几类典型报错。这一节按真实错误信息来对照排查。5.1 TypeError: not enough arguments for format string这个报错通常出现在 pymysql 里原因是 SQL 里有%s占位符但你没传参数或者参数数量对不上# 错误有 %s 但没传参 sql insert into userinfo (username, password) values(%s, %s) cursor.execute(sql) # TypeError解决补上元组参数cursor.execute(sql, (name, password))。还有一种情况是 SQL 里本来就有%字符比如like %abc%这时要把%转义成%%否则驱动会误以为那是占位符。5.2 sqlite3.ProgrammingError: Incorrect number of bindings suppliedsqlite3 里?的数量和元组元素数量不一致时会报这个sql insert into userinfo (username, password) values(?, ?) cursor.execute(sql, (name,)) # 只传了一个报错解决数清楚?有几个元组就放几个。单个参数记得写(name,)。5.3 pymysql.err.OperationalError: (1045, Access denied)这是连接层的认证失败和 SQL 写法无关。检查user、password、host是否正确以及该用户是否有远程连接权限。本地开发常见的是密码填错或 MySQL 用户只允许localhost登录。5.4 401 Unauthorized调用模型接口时如果你在用 TaoToken 的接口辅助生成 SQL 代码遇到 401通常是 API Key 没填、填错或者请求头格式不对。检查Authorization: Bearer 你的Key这个头是否完整。Key 在控制台生成API Keyshttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite5.5 local proxy failed / connection refused这类报错一般是本地网络配置问题比如客户端里填了代理地址但代理没启动或者 Base URL 写错。确认你填的 API 地址是https://taotoken.net/api不要多加路径或斜杠。如果客户端里之前配过代理先清掉再试。5.6 reading choices 相关报错调用对话接口时如果报解析choices失败通常是返回体不是预期的 JSON 结构可能因为请求被拦截、返回了 HTML 错误页或者模型名写错。先打印原始响应体看看到底返回了什么再对照接入文档检查请求参数接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite5.7 OAuth 相关报错如果你用的是 Claude Code 这类工具走的是 Anthropic 兼容入口遇到 OAuth 报错一般是认证方式没选对。这类工具通常支持 API Key 和 OAuth 两种模式用 TaoToken 的话选 API Key 模式Base URL 填ClaudeCodeAnthropichttps://taotoken.net/api/anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite5.8 配置三件套对照不管你用哪种客户端或工具接入时都要确认这三样齐全缺一个就连不上配置项填什么常见错误Base URLhttps://taotoken.net/api多写路径、漏写 httpsAPI Key控制台生成的 Key复制时带空格、Key 过期Model ID文档里列出的模型名名字拼错、用了不存在的模型以 Codex 的auth.json为例配置大致是这样{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: 文档里对应的模型ID }Cline 的 MCP 配置里同样要填全这三项Base URL、Key、Model ID 一个都不能少。CC Switch 切换配置时也检查这三件套是否完整。6. 把参数化写进你的代码习惯从今天起改掉拼接回到最开始那个问题变量怎么加进 SQL 语句。答案已经很清楚了——不要「加进去」而是用占位符留位置把值单独传。sqlite3 用?配元组pymysql 用%s配元组参数多了用命名参数配字典。字符串拼接无论用加号、%还是 f-string只要变量来自外部就必须改掉。我自己的习惯是写完一条 SQL先看里面有没有直接出现的变量。如果有立刻改成占位符。表名、列名这种没法参数化的走白名单校验。批量插入用executemany别循环单条执行。如果你想让模型帮你审查老项目里的 SQL 拼接或者批量生成参数化版本可以用模型对话入口把代码贴进去让它改模型对话https://taotoken.net/api/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite长期做数据库相关开发、需要 Agent 持续辅助的看 Coding PlanCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite最后留一个实操建议把你项目里所有execute调用搜一遍凡是 SQL 字符串里出现了、%、f且后面跟着变量的全部标记出来逐个改。改完用本文第 4 节的恶意输入跑一遍回归确认注入被拦住。这一步花不了多少时间但能挡掉一类很致命的问题。
RELATED

相关推荐

Vue 3 SSR实战:从CSR到服务端渲染,解决首屏性能与SEO收录问题

Vue 3 SSR实战:从CSR到服务端渲染,解决首屏性能与SEO收录问题

我做Vue项目开发这些年,花时间最多、收益最明显的一块,居然是被业务方逼着补上的SSR。起因是一个品牌官网,SEO一直抓不到内容,首页白屏稳定在4秒以上,市场部天天催,开发组内部又僵在“继续加loading动画等S…

📅 2026/10/10 0:59:12
10 分钟跑通 TradingAgents:从 pip 安装到让 AI 帮你分析第一只股票

10 分钟跑通 TradingAgents:从 pip 安装到让 AI 帮你分析第一只股票

10 分钟跑通 TradingAgents:从 pip 安装到让 AI 帮你分析第一只股票 【免费下载链接】TradingAgents-AI.github.io TradingAgents: Multi-Agents LLM Financial Trading Framework 项目地址: https://gitcode.com/GitHub_Trending/tr/TradingAgents-AI.github.io …

📅 2026/10/10 0:59:12
obsidian-better-export-pdf 架构解析:如何逆向 Obsidian 官方打印函数实现 PDF 增强导出

obsidian-better-export-pdf 架构解析:如何逆向 Obsidian 官方打印函数实现 PDF 增强导出

obsidian-better-export-pdf 架构解析:如何逆向 Obsidian 官方打印函数实现 PDF 增强导出 【免费下载链接】obsidian-better-export-pdf Obsidian PDF export enhancement plugin 项目地址: https://gitcode.com/gh_mirrors/ob/obsidian-better-export-pdf o…

📅 2026/10/10 0:59:12
MORE NEWS

更多资讯

📰

N4软件虚拟机安装实战:环境规划、数据库初始化与踩坑记录

简介:N4软件虚拟机安装指导.doc 是一份面向 Niagara 平台开发与楼宇自控工程师的虚拟化部署资料,专门解决在个人电脑上使用 VMware 搭建 Windows 7 x64 环境、安装并启动 N4 软件的问题。文档按实操顺序展开,完整涵盖了新建虚拟机时硬件选型、…

📰

统信服务器操作系统V20(1050a)安装全攻略:架构选型与避坑指南

简介:这份《统信服务器操作系统V20(1050a) 安装手册》是统信软件官方发布的部署指南,面向系统管理员、运维工程师和测试人员,适用于服务器操作系统安装、国产化环境搭建等场景。内容涵盖安装准备、硬件兼容性要求、安装源校验,以及…

📰

Kun 扩展打包、侧载与自定义 Index 权威指南:从 .kunx 包到安全分发全流程

人工智能AI Agent自主智能体桌面应用MCP Clients 【免费下载链接】Kun Local-first AI agent workspace for coding, writing, design, research, and automation — one runtime for desktop GUI and TUI. 项目地址: https://gitcode.com/gh_mirrors/de/Kun 点击查…

📰

Meteor 微博 OAuth 登录包弃用迁移指南:从 `weibo` 切换到 `weibo-oauth` 并理解其 OAuth 2.0 流程实现

后端前端开发工具移动开发 【免费下载链接】meteor Meteor, the JavaScript App Platform 项目地址: https://gitcode.com/gh_mirrors/me/meteor 点击查看 免费下载 本文以 Meteor 仓库中 packages/deprecated/weibo/README.md 为骨架,讲解该包被弃用的…

📰

Zeek DNS 日志增强:如何将 Authoritative 与 Additional 响应纳入 dns.log(auth-addl 脚本深度解析)

网络安全网络IDS 【免费下载链接】zeek Zeek is a powerful network analysis framework that is much different from the typical IDS you may know. 项目地址: https://gitcode.com/gh_mirrors/ze/zeek 点击查看 免费下载 导读:Zeek 的 DNS 分析器默…

📰

Graffle 官方指南导读:为什么选择 Graffle、生成式客户端六大优势与文档体系速览

后端 【免费下载链接】graffle Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere. 项目地址: https://gitcode.com/gh_mirrors/gr/graffle 点击查看 免费下载 Graffle 是一个面向 JavaScript 的通用 GraphQL 客户端&#x…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬