PDO insert data through transaction method
Your code is all right from the performance point of view. 

But again, you are doing the double work with your homegrown wrapper. For the life of me I won't understand a wrapper that makes your life harder.

So again, a code with vanilla PDO instance in $this-&gt;db

$stmt = $this-&gt;db-&gt;prepare('INSERT INTO books_pre 
    (title, author, publication, container, tmp_no, created_by, created_at) 
    VALUES (:B, :C, :D, :E, :A, :user_id, now())');

try {
    $this-&gt;db-&gt;beginTransaction();
    foreach(data as $i =&gt; $row) {
        // skip the first two lines
        if ($i == 0 or $i == 1) {
            continue;
        }
        $row['user_id'] = $_SESSION['user_id'];
        $stmt-&gt;execute($row);
    }
    $this-&gt;db-&gt;commit();
} catch (Exception $e) {
    $this-&gt;db-&gt;rollback();
    throw new $e;
}


Given you already have an associative array we can use its keys as placeholder names. 

PDO's execute() method can accept an associative array with data, for example

$stmt = $pdo-&gt;prepare("SELECT * FROM t WHERE id=:id");
$stmt-&gt;execute(['id'] =&gt; $id);


As you can see, we are sending an array directly into execute(), which  keys that must match placeholder names. Given your array already have keys, I just used them as placeholder names in the query. 

However, it'a a matter of taste and you can keep use your bind() calls instead. 

An important note, there should be no such thing like echo "Failed: " in your code. Always just throw an error and let it be echoed by PHP.

On a side note, a comment like this

    // Start transaction
    $this-&gt;db-&gt;beginTransaction();


makes very little sense. Do not write comments that just duplicate a code below. Use comments to explain something not that obvious.