I've been playing with PDO and got quite a shock, so I thought I'd run this past you all.
I love PDO, and I thought that the "prepare once, execute many" idea behind prepared statements was beautiful.
Now previous to using prepared statements, I used to build up a long SQL 'VALUES' clause by looping through an array of "records" to insert, instead of running an INSERT query on every 'record' in the array.
Sensible enough. So should I now dump that for "multiple executes" in my array loop? Well I tried. Here's my experience...
I'm using a simple 'members' table: id (PK/int) | firstname (varchar/25) | surname (varchar/25) | title (tinyint/1)
$d = ['Osian', 'Williams', 1];
$data = array_fill(0, 1000, $d); //create 1000 identical members
$db = new PDO('mysql:host=localhost;dbname=myDB','root','');
$stmt = $db->prepare('INSERT INTO members (firstname, surname, title) VALUES (?,?,?)');
$s = microtime(true);
for($i=0;$i<count($data);$i++) {
$stmt->execute($data[$i]);
}
$e =microtime(true);
echo ($e-$s);
This test gave repeated results between 3.07 and 3.18 seconds. Perhaps you're wondering why I didn't use a higher number like 10,000?
Well I did and it timed out (>30 seconds). OK, I'm not using transaction/commit, but still that's some serious arse-dragging [EDIT - See First Reply for use of transaction].
So, really, really disappointed with this, I thought I'd try the old way of building a long SQL statement. This isn't quite as straightforward any more as we need to use placeholders to take full advantage of security benefits.
Here's the code:
$d = ['Osian', 'Williams', 1];
$data = array_fill(0, 1000, $d); //create an array of 1000 records
$s = microtime(true);
$db = new mPDO('mysql:host=localhost;dbname=myDB','root',''); //NOTE the extended mPDO class
$stmt = $db->multiPrepare('INSERT INTO members (firstname, surname, title)', $data); //New Method
$stmt->multiExecute($data); //New Method
$e =microtime(true);
echo ($e-$s);
Using a couple of extended classes, each just having a single simple extra method each, I achieved a 1000 inserts in 0.018 seconds. Over 150x quicker.
The 10,000 inserts no longer timed out. They were inserted in 0.15 seconds.
So here's my code. The first class, mPDO just provides the multiPrepare()
method which takes the first half of an INSERT statement (without the VALUES clause) as the first parameter and the data array to be inserted as the second parameter.
What it also does is point the prepare()
and multiPrepare()
methods to create a mPDOStatement object instead of the default PDOStatement object. That's the purpose behind Line 21:
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('mPDOStatement', array($this)));
The second class, mPDOStatement has the multiExecute()
method, just taking the data array as its only parameter.
Please note that the an mPDOStatement created by the multiPrepare()
method should not be executed again unless there are an exact number of 'records' in the next data array to be inserted.
I know this needs some work before implementing for real, so I'm open to suggestions, criticisms, improvements, etc. Also, did I get anything wrong with the testing? Did I miss something? Is there a better way?
Thanks d.