请记住,如果您使用事务,您应该在提交之前使用 lastInsertId
否则它将返回 0
(PHP 5 >= 5.1.0, PHP 7, PHP 8, PECL pdo >= 0.1.0)
PDO::lastInsertId — 返回最后插入行的 ID 或序列值
返回最后插入行的 ID,或者根据底层驱动程序返回序列对象的最后一个值。例如,PDO_PGSQL 允许为 name
参数指定任何序列对象的名称。
注意:
此方法可能无法在不同的 PDO 驱动程序之间返回有意义或一致的结果,因为底层数据库可能甚至不支持自动递增字段或序列的概念。
name
应从中返回 ID 的序列对象的名称。
如果没有为 name
参数指定序列名称,PDO::lastInsertId() 返回一个字符串,表示插入到数据库中的最后一行行的 ID。
如果为 name
参数指定了序列名称,PDO::lastInsertId() 返回一个字符串,表示从指定的序列对象检索到的最后一个值。
如果 PDO 驱动程序不支持此功能,PDO::lastInsertId() 会触发 IM001
SQLSTATE。
如果属性 PDO::ATTR_ERRMODE
设置为 PDO::ERRMODE_WARNING
,则会发出级别为 E_WARNING
的错误。
如果属性 PDO::ATTR_ERRMODE
设置为 PDO::ERRMODE_EXCEPTION
,则会抛出 PDOException。
为了节省一些人的时间。
在使用 MySQL 或 MariaDB 时,如果在一个查询中插入多行(INSERT INTO table (a,b,c) VALUES (1,2,3), (2,3,4), ...),到具有 auto_increment 列的表中,PDO::lastInsertId 不会返回最后一行自动生成的 ID。相反,将返回第一个生成的 ID。这可能很好地解释了 MySQL 和 MariaDB 文档。
引用他们的文档,
MySQL
"如果没有参数,LAST_INSERT_ID() 返回一个 BIGINT UNSIGNED (64 位) 值,表示最近执行的 INSERT 语句的结果,为 AUTO_INCREMENT 列成功插入的第一个自动生成的值."
MariaDB
"LAST_INSERT_ID()(无参数)返回最近执行的 INSERT 语句的结果,为 AUTO_INCREMENT 列成功插入的第一个自动生成的值."
这显然与 lastInsertId 自身文档中所述不符。希望这能帮助某人避免调试 ID 不匹配的原因。
tl;dr (MySQL | Mariadb) + 多行插入 + PDO::lastInsertId = 第一个自动生成的 ID
行为使用 MariaDB 10.2.6 32 位、PHP 5.6.31 32 位和 mysqlnd 5.0.11 在 Windows 7 上运行测试。
在 MySQL 中使用事务时,请注意 lastInsertId()。以下代码返回 0 而不是插入的 ID。
<?php
try {
$dbh = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$stmt = $dbh->prepare("INSERT INTO test (name, email) VALUES(?,?)");
try {
$dbh->beginTransaction();
$tmt->execute( array('user', '[email protected]'));
$dbh->commit();
print $dbh->lastInsertId();
} catch(PDOExecption $e) {
$dbh->rollback();
print "Error!: " . $e->getMessage() . "</br>";
}
} catch( PDOExecption $e ) {
print "Error!: " . $e->getMessage() . "</br>";
}
?>
如果没有抛出异常,lastInsertId 会返回 0。但是,如果在调用 commit 之前调用 lastInsertId,则会返回正确的 ID。
以防有人想知道
类似于
$val = 5;
$sql = "REPLACE table (column) VALUES (:val)";
$stmt = $dbh->prepare($sql);
$stmt->bindParam(':val', $val, PDO::PARAM_INT);
$stmt->execute();
$lastId = $dbh->lastInsertId();
将返回最后插入的 ID,无论记录是替换还是简单插入
REPLACE 语法只是插入,或删除 > 插入
所以 lastInsertId() 仍然有效
参考 https://www.mysqlserver.cn/doc/refman/5.0/en/replace.html
用于 REPLACE 用法
如果您通过 FreeTDS 从 Linux 访问 MSSQL/SQL Server 2008 R2(或更高版本),那么获取最后插入 ID 的方法比下面概述的解决方案要简洁一些。
此处概述了相关的特定 SQL
http://msdn.microsoft.com/en-us/library/ms177564.aspx
例如,对于包含两个列(product_id、product_name)的表,其中 product_id 是 uniqueidentifier 或类似的,您可以执行以下操作。
<?php
// 假设 $dbh 连接句柄已建立
$sql = "INSERT INTO product (product_name) OUTPUT INSERTED.product_id VALUES (?)";
$sth = $dbh->prepare($sql);
$sth->execute(array('widgets'));
$temp = $sth->fetch(PDO::FETCH_ASSOC);
?>
然后 $temp 将包含一个类似的数组
Array
(
[product_id] => E1DA1CB0-676A-4CD9-A22C-90C9D4E81914
)
请注意,根据您选择的 TDS 版本,PDO_DBLIB/FreeTDS 如何处理 uniqueidentifier 列有一些问题,这些问题直到 PHP 5.3.7 才得到解决。
有关此问题和补丁的信息,请访问此处
https://bugs.php.net/bug.php?id=54167
需要注意的是,此函数**不**检索行的ID(主键),而是它的OID。
因此,如果您使用的是最新版本的PostgreSQL,此函数将无法帮助您,除非您在创建表时专门向表中添加OID。
我认为我在PostgreSQL中找到了一个不错的解决方案,可以使用自8.2版本起Postgress提供的RETURNING获取ID。在下面的示例中,我在insert子句中添加了"returning"以及表的主键,然后在执行后,我执行了一次fetch操作,获取了一个包含最后插入的id值的数组。
<?php
public function insert($employee){
$sqlQuery = "INSERT INTO employee(user_id,name,address,city) VALUES(:user_id,:name,:address,:city) RETURNING employee_id";
$statement = $this->prepare($sqlQuery);
$a ="2002-03-11 12:01AM" ;
$statement->bindParam(":user_id", $employee->getUserId(), PDO::PARAM_INT);
$statement->bindParam(":name", $employee->getName(), PDO::PARAM_STR);
$statement->bindParam(":address", $employee->getAddress(), PDO::PARAM_STR);
$statement->bindParam(":city", $employee->getCity(), PDO::PARAM_STR);
$statement->execute();
$result = $statement->fetch(PDO::FETCH_ASSOC);
return $result["employee_id"];
}
?>
此函数现在与较新的MS SQL驱动程序兼容。http://msdn.microsoft.com/en-us/library/ff628155(v=sql.105
对于PostgreSQL用户,请注意!针对ed at hicklinslade dot com的评论,他写道:
...
$last_insert_id = $objPDO->lastInsertId("$strTable_id_seq);
这似乎按预期工作。我不太清楚的是,这是否只是返回序列的当前值;如果确实如此,这并不是代码刚刚插入的记录 ID 的特别可靠指标,尤其是在您的站点或应用程序的流量非常大时。
...
永远不要在 PostgreSQL 序列中使用 lastInsertId(),**尤其是在应用程序的插入/更新负载很高时**。PostgreSQL 序列是非事务性的(一个避免独占锁定的自然设计功能,否则会导致不可接受的性能)。这意味着任何并发事务都会增加同一个序列,这将使 lastInsertId() 返回的值相对于您的事务的最后插入变得无效。例如:
事务 1 使用 nextval('some_seq') 插入,产生 100;
并发事务 2 使用 nextval('some_seq') 插入,产生 101;
事务 1 调用 lastInsertId(),预期为 100,但**得到的是 101**。
这个 PDO 方法对 PostgreSQL 来说是不可取的,始终使用 INSERT ... RETURNING 替代。谢谢。
MySQL/MariaDB 用户,请注意,虽然此函数返回一个字符串,但如果您的列具有 ZEROFILL 属性,则**不会保留前导零**。
$dbh->commit();
print $dbh->lastInsertId();
以上将始终返回零 (0)
因此,在提交事务之前调用 $dbh->lastInsertId() 很重要
以上应修改为
print $dbh->lastInsertId();
$dbh->commit();
在 7.0.9 版本中,我已经实现了 lastInsertId,无需为 PostgreSQL 指定序列名称(我也在 5.6.can'trememberthenumber 中完成了,但我找不到 PR)。
有人可以更新关于它的文档吗?
这是 Pull Request:https://github.com/php/php-src/pull/2014
关于通过类创建的连接
例如:db::SQL()->query();
然后 db::SQL()->lastInsertId();
它将创建一个新的连接,并且不会返回插入的最后一个 ID。最好包含一个 PDO 连接文件(或直接包含登录信息)并使用它来正确获取最后一个 ID。
$db = new PDO(登录信息);
$db->query();
$db->lastInsertId();
我在 MSSQL 中发现的获取最后插入 ID 的最简单解决方案是
<?php
$STH = $DBH->query("SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS int)");
$STH->execute();
$result = $STH->fetch();
print $result[0];
?>
针对 MSSQL 不提供 lastInsertId() 的问题,这是一个变通方案。此方案的设计独立于区域设置。
<?php
$mixRc = false;
try {
// 执行一个复合命令,第二部分输出插入的 ID
$strQuery =
'INSERT INTO t1 (f1,f2) VALUES(v1,v2); SELECT @@IDENTITY AS mixLastId';
// 是的,你的眼睛没问题,不是 exec 而是 query!!!
$objSth = $objDb->query($strQuery);
$mixRc = (is_object($objSth) and $objSth->errorCode() == '00000');
}
catch (PDOException $objException) {
$pdoMsg = $objException->getMessage();
$pdoMsg = iconv("ISO-8859-1", "UTF-8", $pdoMsg);
$strMessage = 'insertRecord: Failed ' .
$strQuery . ', Error Message: ' . $pdoMsg;
doLog(__FILE__, __LINE__, $strMessage);
throw new core_exception_database($strMessage);
}
if ($mixRc === false) return false;
// 复合命令提供一个多行集语句句柄
// 跳过来自 INSERT 命令的第一个(无效)行集
$objSth->nextRowset();
// 提取来自 "SELECT @@IDENTITY" 的行集的第一行
$rowTd = $objSth->fetch(PDO::FETCH_NUM);
if (!is_array($rowTd)) {
doLog(__FILE__, __LINE__,
'insertRecord: $objSth->fetch() returns %s', gettype($rowTd));
return false;
}
$objSth->closeCursor();
$strLastRowId = trim($rowTd[0]); // trim() 用于去除尾部的空字节
// 整数以字符串形式返回,格式取决于区域设置
// 通常以 ",00" 或 ".00" 结尾 - 去除它们
$strLastRowId = preg_replace('/[,.]0+$/', '', $strLastRowId);
// 去除任何剩余的 "." 或 ","(用于千位分隔符)
$strLastRowId = preg_replace('/[,.]/', '', $strLastRowId);
// GUID 不包含 "," 或 ".",将保持不变
return $strLastRowId;
?>
需要注意的是,至少对于使用 InnoDB 表的 MySQL,在事务提交后,PDO 会将最后插入的 ID 报告为 0,只有在提交之前才会报告实际的 ID。
(顺便说一下,MySQL 会在回滚后继续递增 ID 编号)。
简单示例
<?php
try {
$dbh = new PDO('mysql:host=localhost;dbname=test', 'root', 'passowd');
$smf = $dbh->prepare("INSERT INTO test (`numer`) VALUES (?)");
$a = mt_rand(1, 100);
$smf->bindParam(1, $a, PDO::PARAM_INT);
$smf->execute();
print $dbh->lastInsertId().'<br />';
$a = mt_rand(1, 100);
$smf->bindParam(1, $a, PDO::PARAM_INT);
$smf->execute();
print $dbh->lastInsertId();
$dbh = null;
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
?>
小心混合自动增量和显式 ID!
给定一个新的表“tbl”,执行
insert into tbl values (0, 'kaeptn blaubaer'); -- 自动增量 (-> 1)
insert into tbl values (16, 'pipi langstrumpf'); -- 显式 ID (-> 16)
select LAST_INSERT_ID();
将返回 1,这不是最后插入的值。它是最后一次自动增量插入的值!
(使用 mysql)
为了回应 Yonatan Ben-Nes,使用最新版本的 PHP 5.x 和 PostgreSQL 8.x,驱动程序将返回一个“有意义的”ID(而不是 OID),前提是你传递了相应序列的名称。
因此,如果您按如下方式创建了一个表
CREATE TABLE "user" (
"id" SERIAL PRIMARY KEY NOT NULL,
"username" character varying(32)
);
PostgreSQL 将(默认情况下)创建一个名为 'user_id_seq' 的序列。
然后,你可以执行类似以下的操作
$strTable = "user"
$last_insert_id = $objPDO->lastInsertId("$strTable_id_seq);
这似乎按预期工作。我不太清楚的是,这是否只是返回序列的当前值;如果确实如此,这并不是代码刚刚插入的记录 ID 的特别可靠指标,尤其是在您的站点或应用程序的流量非常大时。
正如 Dennis Du Kroger 所说,在这种情况下,该函数将返回 0。
但是,您可以通过执行查询来检索最后插入的 ID,查询要求 LAST_INSERT_ID() 函数(至少在 MySQL 中)。
试试这个
($o_db 是声明的适配器)
$last_id = $o_db->fetchAll('SELECT LAST_INSERT_ID() as last_id');
$last_id = intval($last_id[0]['last_id']);
对于 PostgreSQL,您仍然可以使用旧的解决方案来返回 INSERT 的最后一个 ID,选择表的 id_sequence 的 currval。
以下代码展示了一个工作函数(它很容易适应到另一个类等)。
<?php
// -------------------------
// PDO 与 PostgreSQL 的最后插入 ID
// -------------------------
function pgsqlLastInsertId($sqlQuery, $pdoObject)
{
// 检查查询是否是插入并获取表名
if( preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $sqlQuery, $tablename) )
{
// 获取此表的最后一个序列值
$query = "SELECT currval('" . $tablename[1] . "_id_seq') AS last_value";
$temp_q_id = $pdoObject->prepare($query);
$temp_q_id->execute();
if($temp_q_id)
{
$temp_result = $temp_q_id->fetch(PDO::FETCH_ASSOC);
return ( $temp_result ) ? $temp_result['last_value'] : false;
}
}
return false;
}
?>
使用示例
<?php
// ... 连接到 PostgreSQL 数据库
$pdoObject = new PDO('pgsql:host=localhost;dbname=mydb', 'user', 'pass');
$sql = 'INSERT INTO table (column) VALUES (\'some_value\');';
$result = $pdoObject->prepare($sql);
$result->execute();
echo '最后插入的 ID: ' . pgsqlLastInsertId($sql, $pdoObject);
?>
在“返回值”部分
如果 PDO 驱动程序不支持此功能,PDO::lastInsertId() 将触发 IM001 SQLSTATE。
--------------------------------------------------------------------------------
那么,解决这个问题的方法是什么?
我遇到了这个错误
-------------------------------------------------------------------------------
PHP 警告:PDO::lastInsertId():SQLSTATE[IM001]:驱动程序不支持此函数:驱动程序不支持 lastInsertId() 在 xxxx 中
-------------------------------------------------------------------------------
我使用的是 IBM db2 odbc 驱动程序。