13

私はすでに他の回答を見てきましたが、私の質問は関連性があり、別のエントリに値するとまだ感じています。

settings(ユーザー設定を保存する)という名前のテーブルがあり、ユーザーごとに複数の設定を挿入する必要があります。最初は設定ごとにinsert文を分けて実行していたのですが、これはあまり良い方法ではないと感じたので、同じinsert文で複数行を挿入することを考えました。私の唯一の問題は、新しく挿入された各行の auto_incremented ID が必要なことです。

これは不可能/スケーラブルなどという回答を読みましたが、解決策にたどり着いたと感じています。私のやり方が正しいかどうかのフィードバックが欲しいので、この質問をします。

私がやったことは簡単です。複数の行を挿入した後、last_insert_id() を呼び出して、同時に挿入された行の最初の行の ID を取得します。挿入された行数のカウントが既にあるので、単純に新しい配列を作成し、last_insert_id() で始まり last_insert_id()+n-1 (n は挿入された行数) で終わる ID を入力します。

次の理由から、これはうまくいくと思います。

1.) MYSQL のドキュメントでは、last_insert_id() は接続に依存しており、別のクライアント/接続が新しいレコードを挿入しても、他のクライアントの last_insert_id() には影響しないと記載されています。

2.) 挿入は単一の SQL ステートメントで行われるため、挿入全体を単一のトランザクションとして扱う必要があると思います。そうであれば、ACID ルールが適用され、auto_incremented 値が連続している必要があります。これについてはよくわかりません。

これらが、ロジックが機能するはずだと私が感じる理由です。私の質問は、上記のロジックはすべての条件で機能しますか? すべての状況で正しく動作することを信頼できますか? 私はそれが現在私のために働いていることを知っています。

4

7 に答える 7

1

ギャンブルが好きなら、これをしてください:)

99%確実に書き込むには、テーブルをロックする必要があります。(将来) 2 つのトランザクションが絡み合うことができないかどうかはわかりません。

100% 確実にこれらの値を読んでください。(またはソースの MySQL を分析します) 最良の解決策は、テーブルに日付を追加し、設定を編集して、最新のものを読み取ることです。構造を変更したくない場合は、トリガーhttp://dev.mysql.com/doc/refman/5.0/en/create-trigger.htmlを使用できます。

良い解決策は、すべての設定を更新するか、ペアのみを更新することです: キー - 設定名

于 2013-05-14T12:30:54.930 に答える
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 に答える
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

どうですか:

$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) を使用して 2 つのテーブルを関連付けようとしている場合は、以下のコードのようなものは意味がありません (上記のようなテーブル構造があると仮定します)。

users$user は特定のユーザー ( . 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 を実行して、すべての設定とユーザー情報をまとめることができます。設定テーブルに複数のエントリがあり、users テーブルに 1 つのエントリがあるため、何か特別なことをする必要があるかどうかはわかりませんが、これを台無しにすれば、誰かが私たちの両方をまっすぐに設定できると確信しています。

$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 のような余分な句を使用しただけかもしれません。 . 設定をユーザーに結合すると、ユーザーと、WHERE 句に一致する設定からのすべての可能な値を含む単一の結果が得られるかどうかわからなかったため、SELECT をプルしsettingsて結合しました。users私は、それぞれに 1 つの結果があった場所にしか参加AしたことがありませんBが、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)                         |
+--------------------------------------------------+

CURRENT_TIMESTAMPのデフォルトを作成するtimeか、日付 ('Ymd G:i:s') をそのフィールドに挿入するだけの場合、新しい設定が作成または変更されたときにログに挿入することで、設定の変更を追跡できます。

UPDATE で何も変更されなかった場合に INSERTS を実行するには、2 つの別個のステートメントを使用できます。「INSERT VALUES() ON DUPLICATE KEY UPDATE ...」でも可能ですが、複数の UNIQUE フィールドを持つテーブルでは、この方法は明らかに推奨されません。後者の方法を使用すると、単一のクエリを意味しますが、usersettingUNIQUE (または DISTINCT?) の組み合わせを作成する必要があります。UNIQUEを作成した場合、私が間違っていない限り、テーブルには ='foo'settingを 1 つしか持つことができません。setting2 つのステートメントでそれを行うには、次のようにします。

$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 に置き換えることができますが、最終結果は同じです。INSERT ステートメントは、UPDATE がテーブル内で何も変更しなかった場合にのみ呼び出されます (これは、その設定とそのユーザーのレコードがなかったことを示します)。

これが非常に長い返信であり、元の質問から逸脱していることをお詫びしますが、それがあなたの真の目的/目標に関連していることを願っています. 真の SQL マスターからの、私の SQL ステートメントを改善する方法についてのあなたの応答とコメントを楽しみにしています。

于 2013-06-15T17:38:10.353 に答える
0

@anigelに同意します。一部のトランザクションが混乱しないことを確認することはできません。挿入を個別のクエリに分割し、個々のクエリごとに last_insert_id() を呼び出し、結果を配列に入力できます。

確かに、これにより処理時間が長くなる可能性がありますが、少なくとも競合を確実に回避できます。さらに、これは設定テーブルであるため、クライアント リクエストごとに大量のトランザクションを実行する必要はほとんどありません。

于 2013-05-30T22:42:58.723 に答える