はじめに
PHPでMySQLデータベースを操作する際、INSERT、UPDATE、DELETEなどのクエリが実際に何件のレコードに影響を与えたかを確認したい場面は頻繁にあります。そのような時に使われていたのがmysql_affected_rows
関数です。
重要な注意点:この関数はPHP 5.5.0で非推奨となり、PHP 7.0.0で完全に削除されました。 現在の開発では代替手段を使用する必要がありますが、レガシーコードの理解や移行のために、この関数の動作原理を理解することは今でも価値があります。
mysql_affected_rows関数とは?
mysql_affected_rows
関数は、直前に実行されたINSERT、UPDATE、DELETEクエリによって影響を受けた行数を返すPHP関数でした。この関数は、データベース操作が期待通りに実行されたかを確認するために不可欠な機能を提供していました。
基本的な構文
int mysql_affected_rows([resource $link_identifier])
$link_identifier
(オプション): MySQLコネクション識別子- 戻り値: 影響を受けた行数(整数)、エラー時は-1
基本的な使用例
INSERT文での使用
// データベース接続(古い方式)
$connection = mysql_connect('localhost', 'username', 'password');
mysql_select_db('database_name', $connection);
// INSERT文の実行
$query = "INSERT INTO users (name, email) VALUES ('田中太郎', 'tanaka@example.com')";
$result = mysql_query($query);
if ($result) {
$affected_rows = mysql_affected_rows();
echo "挿入された行数: " . $affected_rows; // 出力: 挿入された行数: 1
} else {
echo "エラー: " . mysql_error();
}
UPDATE文での使用
// 複数のユーザーの状態を更新
$query = "UPDATE users SET status = 'active' WHERE created_date >= '2023-01-01'";
$result = mysql_query($query);
if ($result) {
$affected_rows = mysql_affected_rows();
echo "更新された行数: " . $affected_rows; // 例: 更新された行数: 15
if ($affected_rows > 0) {
echo "更新処理が正常に完了しました。";
} else {
echo "更新対象のレコードが見つかりませんでした。";
}
}
DELETE文での使用
// 古いレコードを削除
$query = "DELETE FROM logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)";
$result = mysql_query($query);
if ($result) {
$affected_rows = mysql_affected_rows();
echo "削除された行数: " . $affected_rows;
// ログ出力
error_log("古いログレコード {$affected_rows}件を削除しました。");
} else {
echo "削除処理でエラーが発生しました: " . mysql_error();
}
実践的な活用例
バッチ処理での進捗確認
function updateUserStatus($user_ids, $new_status) {
$total_processed = 0;
foreach ($user_ids as $user_id) {
$query = "UPDATE users SET status = '" . mysql_real_escape_string($new_status) .
"' WHERE user_id = " . intval($user_id);
$result = mysql_query($query);
if ($result) {
$affected = mysql_affected_rows();
$total_processed += $affected;
if ($affected === 0) {
echo "警告: ユーザーID {$user_id} は更新されませんでした。\n";
}
} else {
echo "エラー: ユーザーID {$user_id} の更新に失敗しました。\n";
}
}
echo "合計 {$total_processed}件のレコードを更新しました。\n";
return $total_processed;
}
データの整合性チェック
function syncUserData($user_data) {
// ユーザー情報を更新
$query = "UPDATE users SET
name = '" . mysql_real_escape_string($user_data['name']) . "',
email = '" . mysql_real_escape_string($user_data['email']) . "'
WHERE user_id = " . intval($user_data['user_id']);
$result = mysql_query($query);
if ($result) {
$affected_rows = mysql_affected_rows();
if ($affected_rows === 1) {
echo "ユーザー情報を正常に更新しました。";
return true;
} elseif ($affected_rows === 0) {
echo "指定されたユーザーが見つからないか、変更がありませんでした。";
return false;
} else {
echo "予期しない結果: {$affected_rows}行が更新されました。";
return false;
}
} else {
echo "更新処理でエラーが発生しました。";
return false;
}
}
注意すべきポイントと落とし穴
1. SELECT文では0が返される
// SELECT文では常に0が返される
$query = "SELECT * FROM users WHERE status = 'active'";
$result = mysql_query($query);
$affected_rows = mysql_affected_rows(); // 常に0
// 正しくはmysql_num_rowsを使用
$num_rows = mysql_num_rows($result); // SELECTの結果行数
2. UPDATE文で実際に値が変更されない場合
// 既に同じ値が設定されている場合、affected_rowsは0になる
$query = "UPDATE users SET status = 'active' WHERE user_id = 1";
// user_id=1のstatusが既に'active'の場合、affected_rowsは0
3. マルチテーブル操作での動作
// JOINを使ったUPDATE
$query = "UPDATE users u
JOIN user_profiles up ON u.user_id = up.user_id
SET u.last_login = NOW(), up.login_count = up.login_count + 1
WHERE u.user_id = 1";
$result = mysql_query($query);
$affected_rows = mysql_affected_rows(); // 実際に更新されたテーブルの行数の合計
エラーハンドリングとデバッグ
基本的なエラーハンドリング
function safeExecuteQuery($query) {
$result = mysql_query($query);
if ($result === false) {
$error_message = "クエリエラー: " . mysql_error() . "\n";
$error_message .= "実行したクエリ: " . $query;
error_log($error_message);
return false;
}
$affected_rows = mysql_affected_rows();
// -1はエラーを示す
if ($affected_rows === -1) {
error_log("mysql_affected_rows()でエラーが発生しました");
return false;
}
return $affected_rows;
}
// 使用例
$affected = safeExecuteQuery("UPDATE users SET status = 'inactive' WHERE last_login < '2023-01-01'");
if ($affected !== false) {
echo "{$affected}件のユーザーを非アクティブにしました。";
}
デバッグ用のヘルパー関数
function debugQuery($query, $expected_rows = null) {
echo "実行クエリ: " . $query . "\n";
$start_time = microtime(true);
$result = mysql_query($query);
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000; // ミリ秒
if ($result) {
$affected_rows = mysql_affected_rows();
echo "実行時間: " . number_format($execution_time, 2) . "ms\n";
echo "影響を受けた行数: " . $affected_rows . "\n";
if ($expected_rows !== null && $affected_rows !== $expected_rows) {
echo "警告: 期待した行数({$expected_rows})と異なります\n";
}
} else {
echo "エラー: " . mysql_error() . "\n";
}
echo "---\n";
return $result;
}
現代的な代替手段
PHP 7.0以降では、以下の方法で同様の機能を実現します。
1. MySQLi拡張を使用(手続き型)
// MySQLi接続
$connection = mysqli_connect('localhost', 'username', 'password', 'database');
// クエリ実行
$query = "UPDATE users SET status = 'active' WHERE created_date >= '2023-01-01'";
$result = mysqli_query($connection, $query);
if ($result) {
$affected_rows = mysqli_affected_rows($connection);
echo "更新された行数: " . $affected_rows;
} else {
echo "エラー: " . mysqli_error($connection);
}
mysqli_close($connection);
2. MySQLi拡張を使用(オブジェクト指向)
// MySQLi接続(オブジェクト指向)
$mysqli = new mysqli('localhost', 'username', 'password', 'database');
if ($mysqli->connect_error) {
die('接続エラー: ' . $mysqli->connect_error);
}
// クエリ実行
$query = "DELETE FROM temp_data WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 DAY)";
$result = $mysqli->query($query);
if ($result) {
$affected_rows = $mysqli->affected_rows;
echo "削除された行数: " . $affected_rows;
} else {
echo "エラー: " . $mysqli->error;
}
$mysqli->close();
3. PDOを使用(推奨)
try {
// PDO接続
$pdo = new PDO('mysql:host=localhost;dbname=database', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// プリペアドステートメントで安全にクエリ実行
$stmt = $pdo->prepare("UPDATE users SET last_login = NOW() WHERE user_id = ?");
$stmt->execute([123]);
$affected_rows = $stmt->rowCount();
echo "更新された行数: " . $affected_rows;
} catch (PDOException $e) {
echo "エラー: " . $e->getMessage();
}
レガシーコード移行戦略
ステップ1: 現状の把握
# プロジェクト内のmysql_affected_rows使用箇所を検索
grep -r "mysql_affected_rows" /path/to/your/project
ステップ2: ラッパー関数の作成
// 移行用のラッパー関数
function getAffectedRows($connection = null) {
// 新しい実装ではMySQLiを使用
if (is_object($connection) && $connection instanceof mysqli) {
return $connection->affected_rows;
}
// レガシーコード互換性のため
if (function_exists('mysql_affected_rows')) {
return mysql_affected_rows($connection);
}
throw new Exception('mysql_affected_rows関数は利用できません');
}
ステップ3: 段階的な置き換え
// 旧コード
$result = mysql_query($query);
$affected = mysql_affected_rows();
// 新コード(MySQLi)
$result = mysqli_query($connection, $query);
$affected = mysqli_affected_rows($connection);
// 新コード(PDO)
$stmt = $pdo->prepare($query);
$stmt->execute();
$affected = $stmt->rowCount();
パフォーマンスと最適化
バッチ処理での最適化
// 非効率な例(旧方式)
foreach ($user_ids as $user_id) {
$query = "UPDATE users SET status = 'processed' WHERE user_id = " . intval($user_id);
mysql_query($query);
$affected += mysql_affected_rows();
}
// 効率的な例(現代的な方式)
$pdo = new PDO('mysql:host=localhost;dbname=database', 'username', 'password');
$stmt = $pdo->prepare("UPDATE users SET status = 'processed' WHERE user_id = ?");
$total_affected = 0;
foreach ($user_ids as $user_id) {
$stmt->execute([$user_id]);
$total_affected += $stmt->rowCount();
}
一括処理での効率化
// さらに効率的な方法:IN句を使用
$user_ids_str = implode(',', array_map('intval', $user_ids));
$query = "UPDATE users SET status = 'processed' WHERE user_id IN ({$user_ids_str})";
$stmt = $pdo->prepare($query);
$stmt->execute();
$total_affected = $stmt->rowCount();
echo "一括で{$total_affected}件を更新しました";
セキュリティ上の注意点
SQLインジェクション対策
// 危険な例(旧方式)
$status = $_POST['status']; // ユーザー入力
$query = "UPDATE users SET status = '$status' WHERE user_id = 1"; // 危険!
mysql_query($query);
// 安全な例(現代的な方式)
$stmt = $pdo->prepare("UPDATE users SET status = ? WHERE user_id = ?");
$stmt->execute([$_POST['status'], 1]);
$affected = $stmt->rowCount();
まとめ
mysql_affected_rows
関数は、データベース操作の成功確認において重要な役割を果たしていましたが、現在は削除されており、より安全で高機能な代替手段を使用する必要があります。
重要なポイント:
- PHP 7.0以降では使用不可
- MySQLi extension または PDO を使用する
- セキュリティ面でプリペアドステートメントが推奨
- パフォーマンス向上のため一括処理を検討する
移行の優先順位:
- PDO(最推奨): より柔軟でセキュア
- MySQLi: MySQL専用だが高性能
- ラッパー関数: 段階的移行に有効
新しいプロジェクトでは最初からPDOまたはMySQLiを使用し、既存プロジェクトでは適切な移行戦略を立てることが重要です。データベース操作の成功確認は、アプリケーションの信頼性に直結する重要な機能なので、安全で保守性の高い実装を心がけましょう。