mysql会话变量@var需在同一会话中先set再使用,mysql -e执行时须拼接为单命令;psql \set仅客户端替换,不可用于ddl;python必须用参数化查询execute(sql, params),禁用f-string拼sql。
mysql里用@variable传值,但脚本一执行就报unknown column
这是最常见的错觉:以为@user_id := 123设完变量,后面select * from orders where user_id = @user_id就能直接用——其实不行。mysql的会话变量在语句级生效,但sql脚本(比如用mysql -e或source执行)默认是“一条条送进引擎”,中间没有隐式延续的上下文。
- 必须把变量赋值和后续查询写在同一个语句块里,或者用
DELIMITER包成存储过程片段(不推荐日常脚本) - 更稳妥的做法是:用客户端工具(如mysql命令行)先执行
SET @user_id = 123;,再执行含@user_id的查询——注意这两句要连着输,不能换session - 如果用
mysql -e "SELECT ..."方式运行,变量根本不可见,因为-e启动的是全新会话;得改用mysql -e "SET @x=1; SELECT @x;"这种单命令拼接
PostgreSQL想用变量?别碰psql的\set,除非你清楚它只在客户端展开
\set user_id 123看起来像设了变量,但它只是psql在发送SQL前做字符串替换。一旦你写SELECT * FROM logs WHERE id = :user_id,实际发给服务端的是SELECT * FROM logs WHERE id = '123'——类型全靠你手动保证,而且不能用于DDL或动态表名。
- 数值类变量必须加单引号包裹再替换,否则
:user_id会被当标识符,报syntax error at or near ":" - 表名、列名这类标识符变量要用
:identifier语法,比如\set tbl_name users+SELECT * FROM :identifier(tbl_name); - 跨多个
\i文件时,\set不自动传递,每个文件开头都得重新\set
Python脚本里拼SQL字符串?cursor.execute("UPDATE t SET x=%s", [val])才是正解
直接用f-string或%格式化拼SQL,哪怕变量来自内部计算,也等于主动绕过参数化防护。一旦变量含单引号、分号或SQL关键字,轻则报错,重则被注入。
- 所有用户可控输入(包括配置文件读取、argparse参数、环境变量)都必须走
execute(sql, params)的第二参数传入 - MySQLdb/PyMySQL用
%s占位,psycopg2用%s或命名占位%(name)s,SQLite用?——别混用,否则报not all arguments converted - 批量插入别写
executemany("INSERT ... VALUES (%s,%s)", data)然后data里塞None——NULL要显式传None,不是字符串"NULL"
SQL文件里要动态切换库或表?别硬编码,用mysql --defaults-file或环境变量注入
写死USE mydb_2024_q3;或FROM sales_jan会让脚本失去复用性。临时改名、切环境时要么全局搜索替换,要么出错。
- MySQL支持
--defaults-file=/tmp/my.cnf,里面写[client] database=mydb_dev,连接时自动选库 - 更灵活的是用shell变量+sed预处理:
sed "s/{TABLE_NAME}/$TABLE/g" script.sql | mysql,但注意别让变量内容带斜杠或$符号 - PostgreSQL脚本可依赖
current_schema,配合SET search_path TO $schema,比硬写schema名安全得多
事情说清了就结束。最常漏掉的一点:不同客户端对变量的作用域理解完全不同——MySQL Workbench的“执行当前语句”和“执行全部”行为不一致,DBeaver里@变量默认关掉,这些细节不查文档就踩坑。










