MySQL CSV Import Fails After 75 Rows Due to Server Limitations

I built a PHP tool that lets users upload CSV files. The script shows a preview of the data and allows field mapping to database columns. Users can select which rows to import using checkboxes. Everything works great for small files.

The issue is my server seems to have limits on how many database operations I can do at once. I can only insert about 75 records before it stops working. My CSV has around 600 rows that need to be imported.

I’m thinking about splitting the data somehow. Maybe process it in chunks or use MySQL’s LOAD DATA INFILE instead of individual INSERT statements. But I’m not sure how to handle this while keeping the user’s checkbox selections for which records to import.

Has anyone dealt with similar server restrictions? What’s the best way to handle large CSV imports when you hit these kinds of limits?

<?php
session_start();

$upload_path = 'uploads/';
$error_msg = '';
$success_msg = '';

if(isset($_POST['action_type']) && $_POST['action_type'] == 'import_data') {
    if(count($_POST["selected_rows"]) > 0) {
        $total_columns = $_POST["column_count"];
        
        foreach($_POST["selected_rows"] as $row_index) {
            $insert_fields = "";
            $field_values = "";
            
            $current_date = date('Y-m-d H:i:s');
            
            // Static fields
            $static_data = "category_id='".$_POST["category_".$row_index]."'";
            $static_data .= ",created_date='".$current_date."'";
            $static_data .= ",owner_id='".$_SESSION['current_user']."'";
            $static_data .= ",record_type='".$_SESSION['import_type']."'";
            
            // Dynamic field mapping
            $dynamic_fields = array();
            for($col = 0; $col < $total_columns; $col++) {
                if(isset($_POST["field_".$col]) && $_POST["field_".$col] != '') {
                    $field_value = mysqli_real_escape_string($connection, $_POST["data_".$col."_".$row_index]);
                    $dynamic_fields[] = $_POST["field_".$col]."='".$field_value."'";
                }
                
                if($_POST["field_".$col] == "customer_name") {
                    $name_check = trim($_POST["data_".$col."_".$row_index]);
                    $valid_record = !empty($name_check) ? 1 : 0;
                }
            }
            
            if(count($dynamic_fields) > 0 && $valid_record == 1) {
                $final_query = $static_data . "," . implode(',', $dynamic_fields);
                
                // Check for duplicates
                $check_query = "SELECT id FROM customers WHERE owner_id='".$_SESSION['current_user']."'";
                for($i = 0; $i < $total_columns; $i++) {
                    if(isset($_POST["field_".$i]) && $_POST["field_".$i] != '') {
                        $check_value = mysqli_real_escape_string($connection, $_POST["data_".$i."_".$row_index]);
                        $check_query .= " AND ".$_POST["field_".$i]."='".$check_value."'";
                    }
                }
                
                $duplicate_check = mysqli_query($connection, $check_query);
                
                if(mysqli_num_rows($duplicate_check) == 0) {
                    $insert_result = mysqli_query($connection, "INSERT INTO customers SET ".$final_query);
                    if($insert_result) {
                        $success_msg = "Records imported successfully!";
                    }
                } else {
                    $error_msg = "Some records already exist in database";
                }
            }
        }
        
        // Clean up uploaded file
        if(file_exists($_SESSION["temp_file"])) {
            unlink($_SESSION["temp_file"]);
        }
    }
}

if(isset($_POST['upload_csv'])) {
    $uploaded_file = $_FILES['csv_file'];
    
    if(empty($uploaded_file["name"])) {
        $error_msg = "Please select a CSV file to upload";
    } elseif(pathinfo($uploaded_file["name"], PATHINFO_EXTENSION) != 'csv') {
        $error_msg = "Only CSV files are allowed";
    } else {
        $target_filename = time() . '_' . $uploaded_file["name"];
        $target_path = $upload_path . $target_filename;
        
        if(move_uploaded_file($uploaded_file["tmp_name"], $target_path)) {
            $_SESSION["temp_file"] = $target_path;
            $_SESSION['import_type'] = $_POST['record_type'];
        } else {
            $error_msg = "File upload failed";
        }
    }
}
?>

The Problem:

You’re experiencing slow import times and potential server timeouts when importing large CSV files into your MySQL database due to numerous individual INSERT queries and duplicate checks for each row. Your current PHP script processes each row separately, leading to hundreds of database round trips which overwhelms your server’s capacity. The user interface relies on checkboxes to select which rows to import, adding complexity to the batching process.

:thinking: Understanding the “Why” (The Root Cause):

The root cause is the inefficient approach of processing each row individually. Each row requires a database query to check for duplicates before inserting, significantly increasing the total execution time. For large CSV files (like yours with ~600 rows), this results in hundreds of database interactions, exceeding your server’s connection limits or triggering execution timeouts. Directly using multiple INSERT statements is not scalable for large datasets.

:gear: Step-by-Step Guide:

  1. Implement Batch Processing with Transactions: This is the core solution. Rewrite your PHP code to process the CSV data in batches. Instead of processing each row separately, group 25-50 rows together and perform a single database transaction involving a bulk INSERT operation. This reduces the total number of database interactions. Use transactions (BEGIN TRANSACTION, COMMIT, ROLLBACK) to ensure data consistency; if any insert fails, the entire batch is rolled back. Utilize INSERT IGNORE or ON DUPLICATE KEY UPDATE within your SQL query to efficiently handle duplicate entries without individual checks.

  2. Handle Checkbox Selections: Store the user’s checkbox selections (the indices of selected rows) in a session variable. When processing the CSV, only include the selected rows in your batches.

  3. Use Prepared Statements: Employ prepared statements for the batch inserts to further improve database performance and security.

  4. Implement AJAX for Progress Updates: To provide feedback to the user without browser timeouts, use AJAX to send progress updates after each batch is processed. You can create a JavaScript progress bar that updates as each batch completes.

  5. Error Handling and Logging: Implement robust error handling and logging mechanisms to catch potential issues during the import process. This is crucial for debugging and ensuring data integrity.

Here’s an example of how to structure the batch processing (replace placeholders with your actual field names and table name):

<?php
session_start();

// ... (rest of your existing code) ...

if(isset($_POST['action_type']) && $_POST['action_type'] == 'import_data') {
    if(count($_POST["selected_rows"]) > 0) {
        $batch_size = 50; // Adjust as needed
        $selected_rows = $_POST["selected_rows"];
        $total_rows = count($selected_rows);

        $stmt = $connection->prepare("INSERT IGNORE INTO customers (field1, field2, field3, ...) VALUES (?, ?, ?, ...)"); // Prepare your statement
        $stmt->bind_param("sss...", $field1, $field2, $field3, ...); //Bind your parameters

        for ($i = 0; $i < $total_rows; $i += $batch_size) {
            $connection->begin_transaction(); // Start transaction
            try {
                for ($j = $i; $j < min($i + $batch_size, $total_rows); $j++) {
                    $row_index = $selected_rows[$j];
                    // ... fetch data for current row ...
                    $stmt->execute(); // Execute prepared statement
                }
                $connection->commit(); // Commit transaction if successful
            } catch (Exception $e) {
                $connection->rollback(); // Rollback if any error occurs
                //Log the error
                echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
                die();
            }
            //Send AJAX response with progress update
            echo json_encode(['status' => 'success', 'progress' => ($i + $batch_size) / $total_rows * 100]);
            ob_flush();
            flush();
        }
        // Clean up uploaded file
        // ...
    }
}

// ... (rest of your existing code) ...
?>

:mag: Common Pitfalls & What to Check Next:

  • Database Connection Limits: Check your MySQL server’s max_connections setting. If it’s too low, increase it (but be mindful of your server’s resources).
  • PHP Execution Time Limits: Ensure your max_execution_time in php.ini is sufficient to handle the entire import process. You might need to increase it, but batch processing significantly mitigates the need for extreme increases.
  • Memory Limits: Verify your PHP memory_limit setting. Loading a large CSV file into memory at once could lead to memory exhaustion. Consider using a streaming approach to process the CSV if memory becomes a constraint.
  • Transaction Size: Experiment with different $batch_size values. Find the optimal balance between performance and the risk of transaction failures.
  • Error Handling: After implementing batch processing, ensure your error handling correctly captures and reports any issues that might occur within the process.

:speech_balloon: Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!

Your problem is running separate duplicate checks and inserts for each row. That’s hundreds of database round trips - any decent server will time out or hit connection limits. I had the same issue with a client portal processing membership data. Fix it with batch processing in transactions. Wrap 25-50 operations in BEGIN/COMMIT blocks and use INSERT IGNORE or ON DUPLICATE KEY UPDATE instead of checking duplicates first. For checkboxes, store selected row indices in a session array and process them in chunks during batches. Add a progress bar by processing batches through AJAX calls - prevents browser timeouts and shows users what’s happening during big imports.

You’re hitting execution timeouts, not database limits. Had the same issue importing product catalogs. Break it into smaller AJAX requests - process maybe 25 records at a time instead of everything at once. Use JavaScript to make sequential calls until you’re done. Store your checkbox selections in the session and track progress with a counter. This keeps each request under the timeout limit while keeping your selection functionality intact. Bump up your PHP max_execution_time if you can, but chunked AJAX works even on shared hosting with tight limits.

Also check memory_limit and max_input_vars in your php.ini. It’s not always execution time - PHP might not handle all those POST variables from 600 checkboxes. Got burned by this during a user import where everything seemed fine but variables got silently truncated.

i’ve dealt with this b4! doing individual inserts kills performance. try batching 50-100 rows at once with prepared statements. for the checkboxes, just build your batch from the selected rows.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.