In my work, I use both MySQL and Oracle. MySQL is like a fighter jet - sleek, fast, powerful. It carries a smaller payload but packs a wonderful punch and is excellent for analyzing Taliban data. Oracle is like a big B-52 bomber. Ancient, not very pretty, and lumbering, it carries such a big payload that even Osama cannot hide from its powerful data-crunching abilities.
One of the headaches we database pilots have is scoping the size of project, and making a recommendation on what database to use. Recently I conducted some benchmarks on the two databases to investigate how best to perform complex processing of millions of records stored in Oracle in the shortest possible time. I considered using in-memory data structures (eg. bypassing the database altogether), and off-loading the data into a faster database such as MySQL.
Here's what I found:
- For high speed data access for a single user, use your own data structures, don't use a database. However make sure you have enough memory for all your data.
- MySQL without transactions is amazingly fast, but still slower than rolling your own data structures.
- MySQL heap (in-memory) tables did not give an exponential improvement in speed like I hoped - it seems that MyISAM table caching is quite efficient.
- Oracle as expected is very scalable, and the results on old Sun hardware are pretty good, but the results on the Win2000 workstation shows that it requires a lot of tuning, otherwise the results are horrible, and you need to be an expert to tune it. Compared to MySQL, the strength of Oracle is still in handling large numbers of transactions safely, which these benchmarks do not test. I hope to get Oracle running on some high-end hardware in the near future and will retest then.
- Using bind variables is actually faster than sending straight SQL in Oracle (formerly I said it was six times slower, but I found a bug in my benchmark - turns out I was commiting on every insert).
- The tight integration of PL/SQL and Oracle is a big advantage. Converting the PHP code to PL/SQL code contributed to a 30% increase in insert performance for Oracle and a 10 times improvement in select speed.
- At first, I was surprised at the PL/SQL results. The Windows 2000 select benchmark was faster than the Sun server, while for inserts, the Sun server was better. I finally figured it out when I realized that the Windows 2000 notebook has a CPU that is twice as fast as the Sun server, so the selects are twice as fast too as the selects are CPU bound when all the data is cached in RAM. Conversely, the Sun server has several hard disks to spread the insert i/o over, so the Sun server beats Windows 2000 flat.
- Another hint derived from the benchmarks is to make sure that all servers are physically close to each other and on the same network. Because in the last test between PHP running on a Win2000 workstation and an Oracle Sun server had to go through a router, the throughput was quite poor.
- The Oracle select timings become slower relative to the inserts the more complex the network gets, probably because selects require a reply, inserts do not.
- Oracle by default does not return the number of records found in a query. The ADOdb library emulates this feature. This was found to slow down performance by at least 20%. We have modified the library to avoid counting when not required. So the general advice is to set $ADODB_COUNTRECS = false when possible. Added 15 Feb 2002.
Conclusions
If I used my own home-brewed database, bypassing MySQL and Oracle, it would be like flying a SR-71 Blackbird, a plane custom built for speed. However there are benefits to using an established system like MySQL or Oracle - better maintainability and ease-of-use. I guess it boils down to how much you are willing to pay and how much time you want to spend - so are you flying coach or private jet?
| Server |
100,000 inserts time (s) |
Inserts/Sec |
50,000 selects time (s) |
Selects/Sec | DBMS Hardware |
| PHP Assoc Arrays (Mem used: 45Mb) |
1.36 |
73,529 | 0.44 | 113,600 | Win2000 |
| MySQL 3.23 Heap (Mem used: 10Mb) |
23.82 | 4,198 | 21.96 | 2,276 | Win2000 |
| MySQL 3.23 MyISAM | 33.42 | 2,992 | 25.91 | 1,930 | Win2000 |
| PostgreSQL 7.2.3 added 2003 |
69.74 | 1434 | 58.33 | 857 | PostgreSQL 7.2.3 on Win2000 |
| Firebird 1.0.2 added 2003 |
no-bind: 137.67 |
726 2321 |
125.33 68.39 |
399 731 |
PHP and Firebird running on Win2000 |
| Oracle 8.1.7 (PHP) |
no-bind: 114.32 |
875 1331 |
77.21 | 648 | E450 Sun Server |
| Oracle 8.1.7 (100% PL/SQL) |
50.60 |
1976 | 11.62 | 4310 | E450 Sun Server |
| Oracle 8.1.7 and PHP |
no-bind: 201.94 bind: 96.94 |
495 1034 |
89.24 57.41 |
560 871 |
Oracle on Win2000 |
| Oracle 8.1.7 (100% PL/SQL) |
73.00 | 1370 | 5.82 | 8591 | Oracle on Win2000 |
| Oracle 8.1.7 |
259.81 |
385 | 232.77 | 215 |
PHP running on Win2000 and Oracle on Sun Server on same network |
| Oracle 8.1.7 |
1002.29 |
100 | 606.03 | 83 |
PHP running on Win2000 and Oracle on Sun Server through a router |
Changes
11 March 2002Tests were done using PHP 4.1.1 and the ADOdb class library, except for the Oracle bind variable test, which used the oci8 libraries directly for speed.
09 Feb 2003
Added benchmarks for PostgreSQL and Interbase/Firebird with 100,000 recs inserted deleted.
Also benchmarked MySQL InnoDB and BDB with for 1000 recs inserted/selected. Results below:
Inserts/Sec Selects/Sec
HEAP 4150 2395
MYISAM 2968 2079
INNODB 3042 2051
BDB 2602 2107
Also tested 1000 records with MS SQL Server 2000:
Inserts/Sec Selects/Sec
MSSQL 1550 931
ODBC 1349 402 (non-bind)
12 Feb 2003
Corrected PL/SQL benchmark calculation for selects/sec.
15 Feb 2003
Code for Oracle on PHP (Win2000) revised upwards due to improvements in ADOdb 3.12.
Create Statements
Here are the Oracle statements used (the MySQL ones are similar):
create table test5000(name varchar(32) not null, price numeric(16,2), stock integer)
create unique index test_idx on test5000 (name) TABLESPACE kbidx
Insert Statement
Not using bind variables (MySQL and Oracle):
$DB->BeginTrans();
for ($i=0; $i < $NUMRECS; $i++) {
$DB->Execute(
"insert into test5000 (name,price,stock) values ('a name $i',$i,$i)");
}
$DB->CommitTrans();
Using bind variables:
$DB->BeginTrans();
$a = '';
$b = 0;
$c = 0;
$stmt = OCIParse($DB->_connectionID,
"insert into test5000 (name,price,stock) values (:a,:b,:c)");
OCIBindByName($stmt,":a",&$a,32);
OCIBindByName($stmt,":b",&$b,32);
OCIBindByName($stmt,":c",&$c,32);
for ($i=0; $i < $NUMRECS; $i++) {
$a = "a name $i";
$b = $i;
$c = $i;
$e = OCIExecute($stmt,OCI_DEFAULT); # bug-fix, added OCI_DEFAULT
if (!$e) {
print $DB->ErrorMsg();
break;
}
}
$DB->CommitTrans();
Select Statement
mt_srand(20);
for ($i=0, $max = $NUMRECS/2; $i < $max; $i++) {
$n = 'a name '.mt_rand(0,$NUMRECS-1);
# retrieve only 1 record
$arr = $DB->getone(
"select price from test5000 where name='$n'");
if (!is_numeric($arr)) {
print "Error searching for '$n'\n";
break;
}
}
PHP Associative Array Code
function getmicrotime()
{
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
}
$NUMRECS = 100000;
$arr = array();
$start = getmicrotime();
for ($i=0; $i < $NUMRECS; $i++) {
$arr["a name $i"] = array($i,$i);
}
$end = getmicrotime() - $start;
printf("Time %4.4f to insert $i records
n",$end);
mt_srand(10);
$start = getmicrotime();
for ($i=0, $max = $NUMRECS/2; $i < $max; $i++) {
$n = 'a name '.mt_rand(0,$NUMRECS-1);
$arr2 = $arr[$n];
if (!$arr2) {
print "Error searching for $n
n";
}
}
$end = getmicrotime() - $start;
printf("Time %4.4f to search for $i records
n",$end);
PL/SQL Insert Benchmark
CREATE OR REPLACE PROCEDURE bench(maxd in pls_integer) ISstartd number; endd number; i PLS_INTEGER; a PLS_INTEGER; b PLS_INTEGER; c PLS_INTEGER; j PLS_INTEGER; pr numeric(16,2); txt varchar(32); begin
/* drop table test5000; create table test5000(name varchar(32) not null, price numeric(16,2), stock integer); create unique index test_idx on test5000 (name); */ delete from test5000; commit;
startd := dbms_utility.get_time; i := 0;
while (i < maxd) loop a := i; b := i; c := i; insert into test5000 (name, price, stock) values ('Test name '|| to_char(a), b, c); i := i + 1; end loop; commit; endd := dbms_utility.get_time - startd;
outp('secs to insert ' || maxd || ' records = '||to_char(endd/100.00));
dbms_random.initialize(10); j := maxd/2; startd := dbms_utility.get_time; for i in 1 ..j loop txt := 'Test name '||to_char(mod(abs(dbms_random.random),maxd)); --outp(to_char(i) || ' ' || txt); select price into pr from test5000 where name=txt; end loop;
endd := dbms_utility.get_time - startd;
outp('secs to search ' || j || ' records = '||to_char(endd/100.00)); EXCEPTION when others then outp(txt); raise; END bench; /
Hardware Tested
E450 Sun Server: PHP 4.1.1 and Oracle on SunOS, 2xSparc 480Mhz, 2 Gb RAM, 6x36Gb HD
Win2000 workstation: PHP 4.1.1 and Oracle on Win2000, Pentium III 833 Mhz, 256 Mb RAM,1x20 Gb HD

