使用PHPMailer批量发送邮件时重复投递问题求助
Hey there, let's figure out why your subscribers are getting multiple copies of the same email—this is a super common gotcha with batch processing, so let's walk through the most likely culprits and fixes step by step.
1. Race Conditions from Unlocked Database Rows
The biggest issue here is probably concurrent reads of your flag=0 subscribers. If your script (or another instance of it) runs before you’ve updated the flag for processed users, those same subscribers get picked up again and re-mailed.
Fix: Use Row-Level Locking with Transactions
Wrap your query and update logic in a database transaction, and use SELECT ... FOR UPDATE to lock the rows you’re processing. This prevents other processes from reading those rows until you’ve marked them as handled. Here’s how that works with PDO:
// Initialize your PDO connection first $pdo = new PDO('mysql:host=your_host;dbname=your_db', 'user', 'password'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); try { // Start a transaction to group read/update operations $pdo->beginTransaction(); // Lock rows to prevent concurrent access $stmt = $pdo->prepare("SELECT id, email FROM subscribers WHERE flag = 0 FOR UPDATE"); $stmt->execute(); while ($subscriber = $stmt->fetch(PDO::FETCH_ASSOC)) { // Add the current subscriber to PHPMailer $mail->addAddress($subscriber['email']); // Send the email if ($mail->send()) { // Mark as successfully sent $updateStmt = $pdo->prepare("UPDATE subscribers SET flag = 1 WHERE id = ?"); $updateStmt->execute([$subscriber['id']]); } else { // Optional: Mark as failed for later retries $updateStmt = $pdo->prepare("UPDATE subscribers SET flag = 2 WHERE id = ?"); $updateStmt->execute([$subscriber['id']]); } // Critical: Clear the recipient list for the next iteration! $mail->clearAddresses(); } // Save all updates once processing is done $pdo->commit(); } catch (Exception $e) { // Roll back if anything goes wrong $pdo->rollBack(); echo "Error: " . $e->getMessage(); }
2. Forgetting to Clear PHPMailer's Recipient List
If you don’t reset PHPMailer’s recipient list after each send, it will keep adding new subscribers to the existing list. By the 4th loop, you’re sending to the first 4 subscribers, and so on—this is an easy fix but easy to miss!
Fix: Add clearAddresses() After Each Send
As shown in the code above, after sending each email, run:
$mail->clearAddresses();
This resets the recipient list so the next loop only targets the current subscriber.
3. Multiple Script Instances Running Simultaneously
If you’re using a cron job or task scheduler, your script might be timing out or restarting before it finishes, leading to overlapping instances processing the same rows.
Fix: Add a Script Lock File
Create a simple lock file to ensure only one instance of your script runs at a time:
$lockFilePath = '/tmp/batch_mail_lock.txt'; $lockHandle = fopen($lockFilePath, 'w'); // Try to get an exclusive non-blocking lock if (!flock($lockHandle, LOCK_EX | LOCK_NB)) { echo "Another batch email process is already running. Exiting."; exit; } // Your entire batch sending logic goes here... // Release the lock when done flock($lockHandle, LOCK_UN); fclose($lockHandle); unlink($lockFilePath);
4. Duplicate Rows in Your Subscribers Table
Double-check if your subscribers table has duplicate email entries with flag=0. Each duplicate will trigger a separate send. Run this query to verify:
SELECT email, COUNT(*) FROM subscribers WHERE flag = 0 GROUP BY email HAVING COUNT(*) > 1;
If you find duplicates, clean up the table or add DISTINCT to your SELECT query.
Quick Best Practices to Avoid This in the Future
- Always use prepared statements (PDO/mysqli) instead of outdated mysql_* functions—they’re safer and more reliable.
- Add a
LIMITto your SELECT query if you have thousands of subscribers, to avoid script timeouts. - Log every send attempt (success/failure) to a separate table—this helps you debug exactly which emails were duplicated.
内容的提问来源于stack exchange,提问作者Haris Khan

