我在Ubuntu 14.04 LTS上使用PHP 5.5.9和MySQL 5.5.44以及mysqlnd 5.0.11-dev。以下陈述未能准备:
$db->prepare("SELECT nr.x FROM (SELECT ? AS x) AS nr")
尽管以下声明成功准备,但仍然如此:
$db->prepare("SELECT nr.x FROM (SELECT '1337' AS x) AS nr")
造成这种差异的原因是什么? The manual表示"参数标记只能在数据值出现的地方使用,而不能用于SQL关键字,标识符等。"但这是一个数据值。
同样的事情发生在独立客户端:
mysql -uredacted -predacted redacted
-- Type 'help;' or '\h' for help.
SELECT nr.x FROM (SELECT '1337' AS x) AS nr;
-- x
-- 1337
-- 1 row in set (0.00 sec)
PREPARE workingstmt FROM 'SELECT nr.x FROM (SELECT ''1337'' AS x) AS nr';
-- Query OK, 0 rows affected (0.00 sec)
-- Statement prepared
DEALLOCATE PREPARE workingstmt;
-- Query OK, 0 rows affected (0.00 sec)
PREPARE brokenstmt FROM 'SELECT nr.x FROM (SELECT ? AS x) AS nr';
-- ERROR 1054 (42S22): Unknown column 'nr.x' in 'field list'
^D
-- Bye
我试图在具有自动递增主键的表中添加行。在InnoDB的默认自动增量锁定模式中,the manual调用"连续",InnoDB在插入行时跳过自动增量值,但不是,就像这样INSERT IGNORE
或ON DUPLICATE KEY UPDATE
的{{1}}或UNIQUE
会遇到INSERT IGNORE
值与要插入的行匹配的现有行。 (这些被称为"混合模式插入"在手册中。)
每隔几个小时,我就会从供应商处导入一个Feed。这有大约200,000行,并且这些行中平均有200行具有与表中已存在的值对应的唯一值。因此,如果我一直使用ON DUPLICATE KEY UPDATE
或INSERT IGNORE
,我会每隔几小时刻录199,800个ID。因此,我不想使用ON DUPLICATE KEY UPDATE
或INTEGER UNSIGNED
,因为我担心随着时间的推移重复插入UNIQUE
可能耗尽42亿的限制BIGINT
BIGINT
1}}键。我不想将列切换到innodb_autoinc_lock_mode
类型,因为32位PHP没有与MySQL INSERT INTO ... SELECT
具有相同语义的类型。服务器管理员不愿意为服务器的所有用户切换到64位PHP或更改INSERT INTO ... SELECT
。
因此,我决定尝试INSERT INTO the_table
(uniquecol, othercol1, othercol2)
SELECT nr.uniquecol, :o1 AS othercol1, :o2 AS othercol2
FROM (
SELECT ? AS uniquecol
) AS nr
LEFT JOIN the_table ON nr.settlement_id = the_table.settlement_id
WHERE the_table.row_id IS NULL
,创建一个包含子查询中唯一键列的1行表,并将其连接到主表以拒绝已存在的唯一键值。 (手册中说["42S22",1054,"Unknown column 'settlement_id' in 'field list'"]
是"批量插入",它不会刻录ID。)目的是做这样的事情:
<?php // MCVE follows
/* Connect to database */
$pdo_dsn = 'mysql:host=127.0.0.1;dbname=redacted';
$pdo_username = 'redacted';
$pdo_password = 'redacted';
$pdo_options = [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',];
$db = new PDO($pdo_dsn, $pdo_username, $pdo_password, $pdo_options);
$pdo_dsn = $pdo_username = $pdo_password = 'try harder';
// ensure that PDO doesn't convert everything to strings
// per http://stackoverflow.com/a/15592818/2738262
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
/* Create mock data with which to test the statements */
$prep_stmts = ["
CREATE TEMPORARY TABLE sotemp (
file_id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
settlement_id VARCHAR(30) NOT NULL,
num_lines INTEGER UNSIGNED NOT NULL DEFAULT 0,
UNIQUE (settlement_id)
)
","
INSERT INTO sotemp (settlement_id, num_lines) VALUES
('15A1', 150),
('15A2', 273),
('15A3', 201)
"];
foreach ($prep_stmts as $stmt) $db->exec($stmt);
/* Now the tests */
$working_stmt = $db->prepare("
SELECT nr.settlement_id
FROM (
-- change this to either a value in sotemp or one not in sotemp
-- and re-run the test program
SELECT '15A3' AS settlement_id
) AS nr
LEFT JOIN sotemp ON nr.settlement_id = sotemp.settlement_id
WHERE sotemp.file_id IS NULL
");
if ($working_stmt) {
$working_stmt->execute();
$data = $working_stmt->fetchAll(PDO::FETCH_ASSOC);
echo "Working: ".json_encode($data)."\n";
} else {
echo "Working statement failed: ".json_encode($db->errorInfo())."\n";
}
$broken_stmt = $db->prepare("
SELECT nr.settlement_id
FROM (
SELECT ? AS settlement_id
) AS nr
LEFT JOIN sotemp ON nr.settlement_id = sotemp.settlement_id
WHERE sotemp.file_id IS NULL
");
if ($broken_stmt) {
$broken_stmt->execute(['15A4']);
$data = $broken_stmt->fetchAll(PDO::FETCH_ASSOC);
echo "Broken: ".json_encode($data)."\n";
} else {
echo "Broken statement failed: ".json_encode($db->errorInfo())."\n";
}
这失败了,给出了PDO错误:
Dog
class Dog(object):
def __init__(self, name, module_name):
self.name = name
# imports module
module = __import__("mylib.tricks.{mod}".format(mod=module_name),
from_list=['arbitrary argument'])
# Dog object gets all tricks from module as bound methods
tricks = module.__all__
for trick in tricks:
exec("self." + trick + "=types.MethodType(" + trick + ", self)")
导致此错误的原因是什么?并且只有在主键不存在且没有耗尽自动增量ID的情况下才有更好的方法来插入行吗?
答案 0 :(得分:5)
您的最新修改使问题非常明确,因此我将尝试回答: 造成这种差异的原因是占位符。
如记录here所示,占位符只能在查询中的某些位置使用。特别是:
参数标记只能用于显示数据值的位置,而不能用于SQL关键字,标识符等。
现在您可能已经注意到SELECT ? as x
准备好了,但不是SELECT nr.x FROM (SELECT ? AS x) AS nr
。这是为什么?好吧,an anonymous author on PHP's doc最好解释一下,所以让我复制/粘贴:
关于预处理语句中的占位符如何工作存在一个常见的误解:它们不是简单地替换为(转义的)字符串,而是执行生成的SQL。相反,DBMS要求&#34;准备&#34;一个语句提供了一个完整的查询计划,用于说明它将如何执行该查询,包括它将使用哪些表和索引,无论你如何填写占位符,它都是相同的。
所以简单地说:因为你在FROM
子句中的子查询中使用占位符,MySQL无法计算查询的执行计划。
换句话说,因为您的查询将始终更改,所以没有&#34;模板&#34;可以为它做好准备。
因此,如果您确实想要使用此查询,则需要使用普通(未准备好的)查询,或者返回PDO的模拟预准备语句。
话虽如此,请考虑评论部分提供的各种备选方案。对于您想要实现的目标,有更好的解决方案。