13

我已经看过其他答案,但我仍然觉得我的问题是相关的,值得单独输入。

我有一个名为 settings 的表(它存储用户设置),我必须为每个用户插入多个设置。最初,我为每个设置执行了单独的插入语句,但觉得这不是一个特别好的方法,我想通过同一个插入语句插入多行。我唯一的问题是我想要每个新插入的行的 auto_incremented ID。

我读过的答案说这是不可能的/可扩展的等,但我觉得我已经找到了解决方案。我想要反馈我的方式是否正确,因此这个问题。

我所做的很简单。插入多行后,我调用 last_insert_id() 来获取同时插入的行的第一行的 ID。我已经知道插入的行数,所以我只需创建一个新数组并使用从 last_insert_id() 开始并以 last_insert_id()+n-1 结束的 ID 填充它(其中 n 是插入的行数)。

我觉得这会起作用,原因如下:

1.) MYSQL 文档指出 last_insert_id() 依赖于连接,如果另一个客户端/连接插入新记录,则不会影响其他客户端的 last_insert_id()。

2.)我觉得由于插入是由单个SQL语句完成的,所以整个插入应该被视为单个事务。如果这是真的,那么应该应用 ACID 规则并且 auto_incremented 值应该是连续的。我不确定这个。

这就是我觉得逻辑应该起作用的原因。所以我的问题是,上述逻辑是否适用于所有条件?我可以依靠它在所有情况下正常工作吗?我知道它目前对我有用。

4

7 回答 7

1

不应依赖这种行为;除了明显的锁定问题,假设您要设置 master<->master 复制;突然之间,id 每次都增加 2。

除此之外,与其实际编写多个插入语句,不如使用准备好的语句:

$db = new PDO(...);
$db->beginTransaction();
$stmt = $db->prepare('INSERT INTO `mytable` (a, b) VALUES (?, ?)');

foreach ($entries as $entry) {
    $stmt->execute(array($entry['a'], $entry['b']));
    $id = $db->lastInsertId();
}

$db->commit();
于 2013-06-07T03:40:49.817 回答
1

如果您喜欢赌博 - 那就这样做吧 :)

要 99% 确定您必须锁定表才能写入。您不确定(将来)两个交易是否能够交织在一起。

100% 确保您阅读了这些值。(或分析源 MySQL) 最好的解决方案是将日期添加到 tablei 编辑设置并阅读最新的。如果您不想更改结构,可以使用触发器http://dev.mysql.com/doc/refman/5.0/en/create-trigger.html

好的解决方案是刷新所有设置或仅刷新对:key - 设置名称

于 2013-05-14T12:30:54.930 回答
0

怎么样:

$id = array();

$sql = "INSERT INTO `table` VALUES ('', 'foo', 'bar');";
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');";
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');";
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');";
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');";
$sql .= "INSERT INTO `table` VALUES ('', 'foo', 'bar');";

if ($db=new mysqli('host', 'user', 'pass', 'dbase'))
{
    $db->multi_query($sql);

    if ( isset($db->insert_id) ) $id[] = $db->insert_id;

    while ($db->more_results())
    {
        if ($db->next_result()) $id[] = $db->insert_id;
        else trigger_error($db->error);
    }

    $db->close();
}
else trigger_error($db->error);

if (count($id) == 0) trigger_error('Uh oh! No inserts succeeded!');
else print_r($id);

也许这会像您想要的那样返回您的插入 ID。我没有测试过它,但也许你可以根据你的目的调整它,谁知道它可能真的有效。

于 2013-06-07T03:25:08.693 回答
0

这个问题一直困扰着我……为什么你需要每行的 insert_id?在我看来,如果您要插入设置表然后将设置与用户相关联,最好向表中添加某种用户密钥。我可能只是没有看到全貌,但这是我得到的想法:

+---------------------------------------+
|  `settings`                           |
+------+--------+-----------+-----------+
|  id  |  user  |  setting  |  value    |
+------+--------+-----------+-----------+
|  `id` INT(11) PRIMARY AUTO_INCREMENT  |
+---------------------------------------+
|  `user` INT(11)                       |
+---------------------------------------+
|  `setting` VARCHAR(15)                |
+---------------------------------------+
|  `value` VARCHAR(25)                  |
+---------------------------------------+

+---------------------------------------+
|  `users`                              |
+------+--------+--------+--------------+
|  id  |  name  |  email |  etc         |
+------+--------+--------+--------------+
|  `id` INT(11) PRIMARY AUTO_INCREMENT  |
+---------------------------------------+
|  `name` VARCHAR(32)                   |
+---------------------------------------+
|  `email` VARCHAR(64)                  |
+---------------------------------------+
|  `etc` VARCHAR(64)                    |
+---------------------------------------+

我在这里的基本想法是,您对 insert_id(s) 所做的事情是如何尝试在用户和设置之间创建引用,以便您知道谁设置了什么,然后您将他们的设置返回给他们。

如果我完全失去了重点,请引导我回到主题上,我会尝试提出另一个解决方案,但是如果我击中任何接近你想要去的地方:

如果您确实在尝试使用 insert_id(s) 关联这两个表,那么类似下面的代码不会更有意义(假设您有一个类似于上面的表结构):

我假设 $user 是对特定用户(users. id)的引用。

我还将假设您有一个名为 $settings 的关联数组,您正在尝试将其放入数据库中。

$mysqli = new mysqli('host', 'username', 'password', 'database') or trigger_error('[MYSQLI]: Could not connect.');

if ($mysqli)
{
    foreach ($settings AS $setting_name=>$setting_value)
    {
        // I'll do individual queries here so error checking between is easy, but you could join them into a single query string and use a $mysqli->multi_query();
        // I'll give two examples of how to make the multi_query string below.
        $sql = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')";
        $mysqli->query($sql) or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']';
    }
    $mysqli->close() // Since we know in this scope that $mysqli was created we need to close it when we are finished with it.
}

现在,当您需要用户的设置时,您可以使用 JOIN 执行 SELECT 以将所有设置和用户信息放在一起,如果我对此感到不满,请原谅我,因为我到目前为止还不是 mysql(i) 专家,所以我我不确定您是否需要做任何特别的事情,因为设置表中有多个条目,而用户表中有一个条目,但我敢肯定,如果我搞砸了,有人可以让我们俩都直截了当:

$mysqli = new mysqli('host', 'username', 'password', 'database') or trigger_error('[MYSQLI]: Unable to connect.');

if ($mysqli)
{

    $sql = "SELECT `users`.`name`, `users`.`email`, `users`.`id`, `users`.`etc`, `settings`.`setting`, `settings`.`value` FROM `settings` JOIN (`users`) ON (`users`.`id`=`settings`.`user`) WHERE `settings`.`user`=$user GROUP BY `settings`.`user` ORDER BY `settings`.`user` ASC";

    if ($result=$mysqli->query($sql))
    {
        if ($result->num_rows == 0)
            echo "Uh oh! $user has no settings or doesn't exist!";
        else
        {
            // Not sure if my sql would get multiple results or if it would get it all in one row like I want so I'll assume multiple which will work either way.
            while ($row=$result->fetch_array())
                print_r($row);  // Just print the array of settings to see that it worked.
        }
        $result->free(); // We are done with our results so we release it back into the wild.
    } else trigger_error('[MYSQLI]: '.$mysqli->error . '['.$sql.']');
    $mysqli->close(); // Our if ($mysqli) tells us that in this scope $mysqli exists, so we need to close it since we are finished.
}

正如我之前所说,我到目前为止还不是 mysql(i) 专家,可能有一些方法可以简化事情,我可能在我的 JOIN 语句的语法上犯了一些错误,或者只是使用了像 GROUP BY 这样的多余子句. 我让 SELECT 从中提取settings并加入users它,因为我不确定将设置加入用户是否会产生一个包含用户和所有可能值的结果,这些结果来自与我们的 WHERE 子句匹配的设置。我只加入AB每个都有 1 个结果的地方,但我相信 JOIN 是在正确的一般区域。

作为多个查询的替代方案,我说我将给出为 multi_query 构建单个查询字符串的示例,因此:

$arr = array();
foreach($settings AS $setting_name=>$setting_value)
{
    $arr[] = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')";
}
$sql = join('; ', $arr);

或者

foreach($settings AS $setting_name=>$setting_value)
{
    $sql = ( isset($sql) ) ? $sql.'; '."INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')" : "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, '$setting_name', '$setting_value')";
}

我要注意的最后一件事:除非您特别需要用户具有相同设置的多个条目,否则您可以在 INSERT 查询之前执行 UPDATE 并且仅在结果 $mysqli->insert_id == 0 时执行 INSERT。如果您尝试为用户保留设置更改的历史记录,这就是您需要多个条目的原因,那么我建议您创建一个单独的表,其中包含一个包含表结构的日志,例如:

+--------------------------------------------------+
|  `logs`                                          |
+------+--------+--------+-----------+-------------+
|  id  |  time  |  user  |  setting  |  new_value  |
+------+--------+--------+-----------+-------------+
|  `id` INT(11) PRIMARY AUTO_INCREMENT             |
+--------------------------------------------------+
|  `time` TIMESTAMP                                |
+--------------------------------------------------+
|  `user` INT(11)                                  |
+--------------------------------------------------+
|  `setting` INT(11)                               |
+--------------------------------------------------+
|  `new_value` VARCHAR(25)                         |
+--------------------------------------------------+

如果您time将 CURRENT_TIMESTAMP 设为默认值,或者只是将 date('Ymd G:i:s') 插入该字段,那么您可以通过在创建或更改新设置时插入日志来跟踪设置的更改。

如果 UPDATE 没有更改任何内容,则要执行 INSERTS,您可以使用两个单独的语句。也可以使用“INSERT VALUES() ON DUPLICATE KEY UPDATE ...”来执行此操作,但显然不建议在具有多个 UNIQUE 字段的表上使用此方法。使用后一种方法意味着单个查询,但是您必须将usersettingUNIQUE (或者可能是 DISTINCT?)结合起来。如果您设置了UNIQUE,那么除非我弄错了,否则setting您将只能在表中有一个='foo'。setting要使用两个语句来做到这一点,您可以执行以下操作:

$sql = "UPDATE `settings` SET `value`='bar' WHERE `user`=$user AND `setting`='foo' LIMIT 1";
$mysqli->query() or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']');
if ( $mysqli->affected_rows == 0 )
{
    $sql = "INSERT INTO `settings` (`id`, `user`, `setting`, `value`) VALUES ('', $user, 'foo', 'bar')";
    $mysqli->query($sql) or trigger_error('[MYSQLI]: ' . $mysqli->error . '['.$sql.']');
}

在上面的代码中,如果您愿意,您可以用 $mysqli->insert_id 替换 $mysqli->affected_rows,但最终结果是相同的。仅当 UPDATE 未更改表中的任何内容时才调用 INSERT 语句(这表明该设置和该用户没有记录)。

很抱歉,这是一个非常冗长的回复,并且偏离了您最初的问题,但我希望它与您的真正目的/目标相关。我期待着您的回复以及关于如何从真正的 SQL 大师那里改进我的 SQL 语句的评论。

于 2013-06-15T17:38:10.353 回答
0

我做了一段时间,但最终没有发现有用的数据所以停止了。

我跟踪表中每个条目的日期时间和 user_who_altered,因此检索列表变得简单。

然而,重要的是设置一个随时间变化的变量,而不是依赖 NOW():

  INSERT INTO table (username,privelege,whenadded,whoadded) VALUES ('1','2','$thetime','$theinserter'),('1','5','$thetime','$theinserter'),etc...;

将很好地插入多行。

检索您寻找的数组:

SELECT idrow FROM table WHERE username='1' AND whenadded='$thetime' AND whoadded='$theinserter';

这是一种很好的做法,因为您能够跟踪哪个用户修改了记录,并且它不依赖于锁定或机会。

至于您自己的解决方案,它将起作用并保存查询。我会担心它如何响应繁忙表上的准备好的语句,也许使用的方法应该考虑到这一点。我使用的方法不受此类问题的影响。

于 2013-06-07T07:08:57.037 回答
0

我同意@anigel。您不能确定某些交易不会混淆。您可以将插入拆分为单独的查询,为每个单独的查询调用 last_insert_id() 并用结果填充数组。

当然,这可能会增加处理时间,但至少可以确保避免冲突。另外,由于它是一个设置表,您不太可能必须为每个客户请求运行大量事务

于 2013-05-30T22:42:58.723 回答
0

我已经这样做了,这是我的应用程序。我在循环中插入记录,当插入每条记录时,我将通过调用 last_insert_id() 将自动增量 id 存储在数组中。所以我可以在需要的地方使用插入的 id 数组。

于 2013-06-07T03:43:37.683 回答