Oracle + PHP

The DeployPHP Series, Part 1: Optimizing PHP and Oracle
by John Lim

In this first installment of the DeployPHP Series, get a checklist for developing and deploying powerful, scalable PHP applications on Oracle.

Downloads for this article:
Oracle Database 10g
Oracle Instant Client
Oracle JDeveloper PHP Extension

The open-source language PHP: Hypertext Preprocessor (PHP) powers some of the most popular Web sites in the world, such as Yahoo!, Lufthansa, and Disney Online. This fact is even more remarkable when you consider that PHP does so much with so little.

Imagine that your Web server is a gigantic virtual factory processing HTTP requests. Now imagine your PHP interpreters as factory workers in hard hats handling the requests. Each worker is enclosed in his/her own cell, unable to talk or coordinate with the others. The workers are absent-minded and have no memory of previous transactions; before any work can commence on the incoming HTTP request, they have to recall all session information from a data store. And after finishing a request, workers have to scramble to tuck away all session information back into the store.

Paradoxically, this stateless environment where workers are isolated in cells and have poor memories is extremely scalable. PHP processes that are totally independent of each other and do not maintain state are highly parallelizable. Of course you need to perform data synchronization and session storage, but you can outsource these parts to a specialized external module, typically a relational database such as Oracle. As Rasmus Lerdorf points out in his OTN article "Do You PHP?", "PHP encourages you to push scalability issues to the layers that require it. If you need a shared data store, use a database that supports replication and can scale to the levels you need."

As a PHP developer, you know that the database is probably the most important component with which you will be interacting. In this first installment in the DeployPHP Series, I'll discuss this checklist for developing and deploying powerful and scalable PHP applications on Oracle (I will assume that you have some knowledge of PHP and SQL, but not much of Oracle):

  1. Choose the right extension API.
  2. Know when to fetch and hint.
  3. Be smart about binding.
  4. Use a class library.
  5. Learn the art of creating optimizer hints and indexes.
  6. Steward your data and manage your business logic.
  7. Keep in touch with "mission control."
Detailed information about optimizing your PHP code post-deployment is covered in the next installment in this series.

1. Choose the Right Extension API

PHP offers several application-programming interfaces (APIs) for connecting to Oracle—namely, the Oracle, OCI8, and in PHP 5.1, the PDO, extensions. As you will see shortly, PHP offers great functionality but awfully inconsistent naming conventions. Here is some guidance about which extension to deploy:

  • Strangely enough, the "Oracle" extension is designed for Oracle7 and should be avoided at all costs. It is obsolete and lacks much functionality such as Large Object (LOB) support.
  • PHP Data Objects (PDO) is the new portable database API in the PHP 5.1 release. However, it is still immature.
  • On Windows, you can choose to query Oracle via the Open Database Connectivity (ODBC) extension. ODBC has some merits, as it provides built-in connection pooling. But the limited ODBC API means that handling of LOBs and IN OUT parameters in stored procedures can be problematic.
  • The OCI8 extension provides the greatest functionality, as it maps very closely to the Oracle Call Interface (OCI). The "8" in OCI8 is misleading; the API can be used with Oracle8/8i, Oracle9i, and Oracle 10g. For best performance, I recommend this API.
For detailed instructions for installing Oracle and PHP on Linux, Windows, and Mac OS X, visit the PHP Developer Center. If you prefer a prepackaged solution for integrating Oracle and PHP, the forthcoming Zend Core for Oracle libraries will be worth looking into.

2. Know When to Fetch and Hint

As a PHP developer, your most common task is retrieving and displaying data from Oracle. Before you start coding or deploying your PHP applications, you need to know a few important techniques.

First, some theory. When you send your SQL to Oracle, Oracle's parser will examine the SQL to see if it can use an existing compiled query. If there is a match, it will use the compiled query (a soft-parse). If no match occurs, then it will compile the query (a hard-parse), running the optimizer to select the best execution plan for the query.

Pagination. Typically you would break up the records you retrieve into pages and retrieve only the current page. You can do this using nested selects and the special ROWNUM variable. The following code shows you how to retrieve a range of records for pagination. Assume that $currentPage starts with page 0:

$firstRec = $currentPage * $recordsPerPage + 1;
$lastRec = ($currentPage+1) * $recordsPerPage;

$myquery = "SELECT * FROM customer WHERE type='HIGHVAL' ORDER BY name";

$sql = " SELECT * FROM 
     ( SELECT a.*, RowNum as rNum 
FROM ( $myquery ) a
            WHERE RowNum <= $lastRec )
     WHERE $firstRec <= rNum";
Each record coming from Oracle is assigned a row number, which you can use to cut off record retrieval. For example, you could use $firstRec <= rNum as the lower bounds and RowNum<= $lastRec as the upper bounds.

Counting records. Another common task that is harder than it looks is maintaining a count of the number of records for specific areas of interest. Oracle, like all high-performance databases, does not return a count of all the records in a returned recordset. This is a problem if want to find out how many records a query will return without running the potentially expensive query.

One example where this is useful is when you want to display a set of scrolling links when paging through a recordset, as shown below:

The simplest solution is to perform the query anyway and count the records as they are returned. A better solution is to perform a query rewrite, changing the original SQL from
SELECT id, msg_subject, msg_contents FROM largetable
to
SELECT COUNT(*) FROM (SELECT id, msg_subject, msg_contents FROM largetable)
If your data is very large or the queries are fairly standard, you can consider using a special counter table and INSERT/DELETE triggers that update the counter table. When you want a count, query the counter table.

Hints. Oracle's query optimizer tries to be intelligent, but it won't always choose the best plan. To minimize these situations, you can supply hints to the optimizer. For example:

$sql = "SELECT * FROM (
SELECT /*+ FIRST_ROWS INDEX(customer ctype) */ * FROM customer 
WHERE type='High Value' ORDER BY name
) WHERE ROWNUM <= $lastRecordToDisplay";
Optimizer hints are placed just after the SELECT keyword, embedded within a comment, triggered by /*+. In the above example, you'll see two hints:

FIRST_ROWS Tells the optimizer that we don't want to retrieve all rows, so it optimizes for speed of retrieval a small fraction of the total records required.
INDEX(tablename indexname) Asks the optimizer to use this index.

These are the two most common hints I use; you can find many others in the Oracle Database SQL Reference, under "Comments" in Chapter 2.

Prefetch. Finally, there is an OCI8 function that allows you to significantly reduce network traffic by setting the number of records to prefetch over the network into your Oracle client buffers. The default of 1 is far too low, as this means that every single record retrieved generates substantial network traffic. You should set it to your typical page size—for example, 20 to 50 records. This optimization alone can speed up queries by 100 to 200 percent.

In PHP:

$conn = OCILogon($user,$pwd);
$stmt = OCIParse($conn,$sql);
OCISetPrefetch($stmt, 20);
OCIExecute($stmt);
While (OCIFetchInto($stmt,$arr)) {
	/* Process $arr */
}
3. Be Smart About Binding

Have a look at the following SQL statements.

SELECT * FROM customer WHERE custid=1
SELECT * FROM customer WHERE custid=249
SELECT * FROM customer WHERE custid=6380
By default, Oracle will use a different execution plan for each one. This approach wastes time and resources because all these statements are variations on the same query. Instead, you can reformat the above SQL to use a bind variable, :var, and bind the values 1, 249, and 6380 to that variable. Then all the SQL becomes identical and can share the same execution plan:

Bind value to variable SQL
bind 1 to :var
bind 249 to :var SELECT * FROM customer WHERE custid=:var
bind 6380 to :var

Translating this to PHP code, binding $value to :var, you'll get:

OCIParse($conn, 'SELECT * FROM customer WHERE custid=:var');
OciBindByName($stmt, ":var", $value,32); # 32 is an arbitrary large size

$value = 1; OCIExecute($stmt);
DisplayResults($stmt);

$value = 249; OCIExecute($stmt);
DisplayResults($stmt);

$value = 6380; OCIExecute($stmt);
DisplayResults($stmt);
Another advantage of binding is that it reduces the risk of SQL injection attacks because attackers will not be able to manipulate the SQL statement in unexpected ways.

Cursor sharing. If you have lots of legacy SQL that does not use binding, you can still reduce the compilation overhead using the CURSOR_SHARING parameter, available in Oracle8 and later. Executing the following SQL statement will cause Oracle to rewrite all your queries to use bind variables:

ALTER SESSION SET CURSOR_SHARING='FORCE'
So the SQL:
SELECT * FROM customer WHERE custid=6380
will be automatically converted to a bindable version before it is passed to the parser:
SELECT * FROM customer WHERE custid=:SYS_B_0
CURSOR_SHARING allows sharing and the reuse of execution plans, but it has some limitations. It is much more efficient to use bind variables yourself. Also, you could still be vulnerable to SQL injection attacks.

On Oracle9i or later, you can also set CURSOR_SHARING='SIMILAR'. This parameter uses a more intelligent algorithm, taking table statistics into consideration to choose whether to convert a literal to a bind variable.

ALTER SESSION affects only the current session, so you will need to call it every time you connect to the database. To make it permanent, ask your DBA to place it in your database parameter file (init.ora or spfile).

In the next section, I'll show you some benchmarks with and without bind variables. The benchmarks will illustrate how using bind variables improved performance by over 100% in an INSERT statement scenario.

4. Use a Class Library

Before you start coding your application, you will find it useful to create a set of PHP functions to encapsulate the low-level functionality of the OCI8 extension. You can choose to either roll your own or use one of the popular open source libraries. The two most popular are PEAR DB and ADOdb. (I am the lead developer of the latter.)

As an example, consider the following code to update a table consisting of three fields: a, b, and c.

$conn = ocilogon('scott','tiger'); 

$stmt = OCIParse("insert into abc (a,b,c) values (?,?,?)");
OCIBindByName($stmt, ":a", $a, 32);
OCIBindByName($stmt, ":b", $b, 32);
OCIBindByName($stmt, ":c", $c, 32);
for ($i=0; $i<TIMES; $i++) {
	$a = $i;
	$b = "b".rand();
	$c = "c".rand();
	ociexecute($stmt,OCI_DEFAULT);
}
ocicommit($conn);
Contrast this code with the simpler PEAR DB version:
include('DB.php');
$DB = &DB::Connect('oci8://scott:tiger@/'); 

$DB->autoCommit(false);
$stmt = $DB->prepare("insert into abc (a,b,c) values (?,?,?)");
for ($i=0; $i<TIMES; $i++) {
	$DB->execute($stmt,array($i,"b".rand(),"c".rand()));
}
$DB->commit();
Or the really cool ADOdb version:
include('/path/to/adodb/adodb.inc.php');
$DB = NewADOConnection('oci8://scott:tiger@/');

$DB->BeginTrans();
$stmt = $DB->Prepare("insert into abc (a,b,c) values (:0,:1,:2)");
for ($i=0; $i<TIMES; $i++) {
	$DB->_Execute($stmt,array($i,"b".rand(),"c".rand()));
}	
$DB->CommitTrans();
You can see that the ADOdb libraries hide nitty-gritty details such as OCIBindByName from you. The libraries also have debugging modes that allow you to log and output the generated SQL for easy problem diagnosis.

Here are some benchmarks I did with the above code where TIMES = 1000. I measured the time from the beginning to the end of the transaction. I used PHP 4.3.11 and Oracle Database version 9.2 on Windows XP. Lower times are better.

OCI8 with binds 0.18s
ADOdb 0.27s
PEAR DB 0.35s
OCI8 without binds 0.41s

As you can see, there is a significant overhead to not using bind variables, particularly with INSERT and UPDATE statements that modify single records. There is some overhead to using a library; this performance hit has to be weighed against the improved ease-of-use and maintainability of your code when using a high-level library.

Another interesting point is that if the benchmarks are done with autocommit enabled, where a commit is silently executed after every INSERT statement, performance is substantially slower. The OCI8 with binds benchmark took 2.92 seconds, or was 16 times slower!

There are several additional advantages to using ADOdb. ADOdb has been heavily tuned for Oracle; for example:

  • ADOdb sets OCISetPreFetch for you automatically.
  • ADOdb inserts Oracle optimizer hints where possible to speed up your code.
  • ADOdb supports IN OUT parameters in stored procedures and LOBs.
  • ADOdb allows you to cache recordsets of frequently used queries. This is very useful for reducing your database load by caching lookup values that rarely change.
  • ADOdb provides session-management utilities, allowing you to store your session information in a character LOB.
  • ADOdb provides a tuning infrastructure that goes beyond SGA Trace. All SQL queries can be captured and cross-referenced by HTTP request, by cost, and by frequency of execution. This approach allows you to pinpoint performance problems in your SQL queries very quickly. (More on this later.)
  • The default ADOdb install is purely portable PHP. For better performance, you can install the optional ADOdb extension, where parts of ADOdb are rewritten in C for speed. This extension is not required in the default install.

Although this is not an Oracle-specific optimization, you should also install a PHP accelerator cache such as Zend Accelerator, eAccelerator, or APC to speed up the compilation and loading of PHP code libraries.

BLOB management. Storing large amounts of data (anything over 4,000 bytes) requires you to use Oracle's daunting LOB API. ADOdb gives you simpler UpdateBlob and UpdateClob functions:
$db->Execute("insert into aTable (id, name, ablob) 
                                values (999, 'Name', null)");

$db->UpdateBlob('aTable', 'ablob', $blobValue, 'id=999');

The above code inserts a new record into table ATABLE and then calls UpdateBlob to store the $blobValue into the field ablob where id=999.

5. Learn the Art of Creating Optimizer Hints and Indexes

Once you start testing your application and in the initial phase of deployment, you'll probably discover unexpected bottlenecks and problems. End users have a funny ability to generate the weirdest bugs in the most unexpected places. One of the best ways of working out why your SQL is slow is to log all SQL statements and execution times and then view the execution plans of the most expensive SQL.

Assuming that your data is properly normalized, the art of improving query performance largely consists of giving hints to the query optimizer and possibly adding better indexes for the query to use.

Oracle allows you to view a high-level version of the execution plan using the EXPLAIN PLAN command. To run an EXPLAIN PLAN for $sql, select an arbitrary $id and run the following statement:
$id = 'MyID';
$explainSQL = "EXPLAIN PLAN SET STATEMENT_ID='$id' FOR $sql";
$stmt = OCIParse($conn, $explainSQL);
OCIExecute($stmt);
The execution plan is saved into a special PLAN_TABLE for that given statement_id.
CREATE TABLE PLAN_TABLE (
  STATEMENT_ID                    VARCHAR2(30),
  TIMESTAMP                       DATE,
  REMARKS                         VARCHAR2(80),
  OPERATION                       VARCHAR2(30),
  OPTIONS                         VARCHAR2(30),
  OBJECT_NODE                     VARCHAR2(128),
  OBJECT_OWNER                    VARCHAR2(30),
  OBJECT_NAME                     VARCHAR2(30),
  OBJECT_INSTANCE                 NUMBER(38),
  OBJECT_TYPE                     VARCHAR2(30),
  OPTIMIZER                       VARCHAR2(255),
  SEARCH_COLUMNS                  NUMBER,
  ID                              NUMBER(38),
  PARENT_ID                       NUMBER(38),
  POSITION                        NUMBER(38),
  COST                            NUMBER(38),
  CARDINALITY                     NUMBER(38),
  BYTES                           NUMBER(38),
  OTHER_TAG                       VARCHAR2(255),
  PARTITION_START                 VARCHAR2(255),
  PARTITION_STOP                  VARCHAR2(255),
  PARTITION_ID                    NUMBER(38),
  OTHER                           LONG,
  DISTRIBUTION                    VARCHAR2(30)
);
As I mentioned earlier, ADOdb provides functionality to log and capture all executed SQL and display the EXPLAIN PLAN from your Web browser. You can enable logging in ADOdb by calling the LogSQL method:
$DB->LogSQL(true);
When logging is enabled, all SQL is dumped into a special ADODB_LOGSQL table, together with execution times and the URL path of the HTTP request. You can examine the logged SQL using the ADOdb performance-monitoring user interface (UI), which is invoked with the following code:
<?php
include_once('/path/to/adodb.inc.php');
session_start(); # session variables required for monitoring
$conn = ADONewConnection('oci8');
$conn->Connect($tnsname,$user,$pwd);
$perf =& NewPerfMonitor($conn);
$perf->UI($pollsecs=5);
?>
Here's a screenshot of the UI:

For example, let's say you click on the first SQL statement, causing ADOdb to run EXPLAIN PLAN on the following SQL:
select * from (SELECT /*+FIRST_ROWS*/ * FROM myhist WHERE accno ='7314028790' 
ORDER BY POSTDT desc) where rownum <= :adodb_offset
The results of the EXPLAIN PLAN will be read from PLAN_TABLE and displayed by ADOdb:
OPERATION OBJECT_NAME COST CARDINALITY BYTES
SELECT STATEMENT   63 456 39216
--COUNT STOPKEY        
----VIEW   63 456 39216
------SORT ORDER BY STOPKEY   63 456 39216
--------TABLE ACCESS FULL   55 456 39216
Scripts Affected: 4 localhost/juris/z/tloan/myloan.php
In the above example, you'll see that although you are selecting records for only one account number (ACCNO), the query is performing a TABLE ACCESS FULL without going through an index. This behavior suggests that MYHIST.ACCNO needs to be indexed.

COUNT STOPKEY means that the query will stop execution when rownum <= :adodb_offset. Also note that the page where the SQL was executed is displayed (myloan.php), together with the number of times the SQL was executed: four times in this case. "Cardinality" is the estimated number of records affected, and "Bytes" is the estimated number of bytes to be processed.

To index ACCNO, use:
CREATE INDEX ON myhist(accno)
After indexing, rerun the EXPLAIN PLAN. Now Oracle is using the newly created index on ACCNO:
OPERATION OBJECT_NAME COST CARDINALITY BYTES
SELECT STATEMENT   10 456 39216
--COUNT STOPKEY        
----VIEW   10 456 39216
------SORT ORDER BY STOPKEY   2 456 39216
--------TABLE ACCESS BY INDEX ROWID MYHIST 2 456 39216
----------INDEX RANGE SCAN ACCNO 1 182  
The good news is that the estimated cost of the query has gone down from 63 to 10, thanks to the index.

6. Steward Your Data and Manage Business Logic

Before you begin coding or deployment, you should take great care in designing your database structures. When stewarding your data, the most important thing to do is to organize your data properly. Here are a few tips:

Use auto-incrementing fields. Auto-incrementing fields are useful as primary keys. In Oracle, these are implemented using triggers and sequences, which are special functions that generate monotonically increasing numbers.

Let's create an auto-incrementing field A_ID for table ATABLE.
CREATE SEQUENCE aSEQUENCE;

CREATE OR REPLACE TRIGGER A_ID
BEFORE INSERT ON ATABLE
FOR EACH ROW
BEGIN
 SELECT aSEQUENCE.nextVal INTO :NEW.A_ID FROM dual;
END;
Every time a record is created, the trigger will activate and insert a new value into A_ID. To retrieve the current value of aSEQUENCE from PHP, typically to get the last inserted value (using ADOdb), simply run:
$id = $DB->GetOne('SELECT aSEQUENCE.currVal FROM DUAL');
DUAL is a special Oracle table used when you want to call a function and retrieve the result as a recordset.

Normalize your data. Normalization is beyond our scope here, so suffice to say that your data should be designed so that all records have a logical primary key. Furthermore, you should split any records in a single table that contain one-to-many relationships within the record into two or more tables.

For example, if you have a table AUTHOR with the following fields:
AUTHOR: AuthorName Address Title1 ISBN1 Title2 ISBN2 Title3 ISBN3
The fields Book1, Book2, and Book3 have a one-to-many relationship with AuthorName, which will create challenges in the future, such as handling authors with more than three titles to their name.

To avoid these problems, you should split the table into AUTHOR and BOOK:
AUTHOR: AuthorName Address
BOOK:   ISBN AuthorName Title
Use indexes. Choose your indexes carefully; they give you the greatest performance boost of all.

A common beginner mistake is to create individual indexes for every important field. But if your most common query uses three fields with three separate indexes, Oracle will most likely choose to use only one of the indexes. In this scenario, it probably makes more sense to create a single compound index having all three fields.

Use EXPLAIN PLAN and a SQL logging facility, as discussed in the previous section, to check how your queries perform after indexing.

Use index-organized tables. If your data is very frequently accessed in such a way that records are retrieved in a clustered pattern, you should consider using index-organized tables (IOTs), also known as clustered indexes. In typical tables, the primary key index is stored in one file and the data in another. In an IOT, the data is stored within the primary key index. As the index leaves are stored sequentially, retrieving data of consecutive records based on the primary key is especially fast.

Here's an example of where an IOT is useful: You plan to write a threaded forum in PHP where all posts and their associated replies are displayed. It makes sense to store posts and associated replies close together on the hard disk; then only a few disk blocks need to be to read to retrieve the whole thread. You can do this by creating the IOT with the following compound key:
(id of the first post of the thread, the actual id of the post)
Manage business logic. You can certainly write your key business logic in PHP, but writing it in PL/SQL instead has the following advantages:
  • Centralized control of key business logic. If you run a server farm, you don't need to update all PHP scripts on all servers to roll out a change.
  • Possibly better performance compared to coding it in PHP, due to tighter coupling between PL/SQL and Oracle's database
  • The same function is now accessible to other languages that have Oracle interfaces, such as Java, Python, and Visual Basic.
For example, imagine you are developing a shopping-cart application. You have a large number of products, and some products are eligible for special discounts if bundled. Your pricing is further complicated because some customers are eligible for bulk discounts.

You have a PHP function called CalcShoppingCartTotal($cartID) to capture this business logic. It returns the total value of the shopping cart. As time passes, this function becomes a performance bottleneck, and you need to make this function available to users running Java-based software. This is an ideal function to migrate to PL/SQL:
CREATE OR REPLACE FUNCTION CalcShoppingCartTotal(
cartid IN number) RETURN number
IS
begin
/* some code */
end;
Once you have rewritten the procedure in PL/SQL, calling it is simple using ADOdb:
$total = $DB->GetOne("SELECT CalcShoppingCartTotal(:0) FROM DUAL",array($cartid));
Now suppose the business requirements have changed, and CalcShoppingCartTotal() has to return multiple values, namely shipping code and total. Consequently, you need to rewrite it as a procedure that has two new OUT parameters:
CREATE OR REPLACE PROCEDURE CalcShoppingCartTotal(
cartid IN number, 
shipcode OUT number,
total OUT number)
IS
begin
/* some code */
end;
You can execute this stored procedure in ADOdb using:
$sql = "begin CalcShoppingCartTotal(:cartid, :shipcode, :total); end;";
$stmt = $DB->PrepareSP($sql);
$DB->InParameter($stmt, $cartid, 'cartid');
$DB->OutParameter($stmt, $shipCode, 'shipcode');
$DB->OutParameter($stmt, $cartTotal, 'total');
$DB->Execute($stmt);
print "<p>total = $cartTotal, shipping code=$ shipCode</p>";
7. Keep in Touch with "Mission Control": Persistent Connections and Apache/IIS

Database logon is an expensive procedure, in terms of both time and resources required. Given that a Web page typically executes within a fraction of a second, reducing this overhead is critical.

PHP tries to avoid this overhead by using persistent connections. PHP maintains a pool of persistent database connections that are allocated to your PHP script when required. This recycling of persistent connections occurs when you call the OCI8 functions OCIPLogon or OCINLogon, or the ADOdb functions PConnect or NConnect.

Persistent connections result in PHP generating a large number of database connections, proportional to the following:
number of Apache or FastCGI processes * number Oracle user id's used
So if you use PHP to connect to a database using two Oracle userids and Apache is configured to run with 256 child processes, then you can expect up to 256 * 2 = 512 database connections per Web server.

As the number of Web servers in your server farm grows, you can expect a very large number of connections to be created. Oracle by default dedicates substantial resources (2 to 3MB is common) for each connection, so this could severely affect database performance.

Another problem is that if you restart your database server, you will need to restart your Web servers too, as existing persistent connections will no longer work.

An alternative solution is to stop using persistent connections on the PHP side and use middleware or database-side connection pooling. To use nonpersistent connections in PHP, connect using OCI8's OCILogon or ADOdb's Connect function.

Middleware such as ODBC or the open source software SQLRelay provides connection pooling that recycles database connections in a more intelligent fashion; both drivers can define an upper bound to the number of persistent connections. However, this approach can involve substantial recoding to the ODBC or SQLRelay APIs.

Another good solution is to use Oracle's Shared Server technology (previously called MTS). This approach creates a pool of shared database processes that Oracle recycles as incoming network requests occur. The method inverts the PHP persistent connections concept by having Oracle recycle its database server resources instead of PHP. You will probably need to consult your Oracle DBA to get this working, but the performance benefits are substantial when you are dealing with a large number of connections.

Final Advice

For those of you using Apache or IIS as your Web server, I have found that PHP and Oracle offer the best performance when Apache is running in prefork mode or when you are running IIS with PHP configured to run using FastCGI.

In these scenarios, each PHP interpreter is running in an independent process, as I described in the beginning of this article. PHP can also run in multithreaded mode, but highly granular locks and the fact that some PHP extensions are not thread-safe means lower performance and reliability.

Apache 1.3 runs in prefork mode by default on UNIX. If you are using Apache 2.0, consult your documentation to ensure that you are running prefork. If you are running on Windows, you can obtain information on FastCGI and PHP from phplens.com/phpeverywhere/fastcgi-php. Zend's WinEnabler is a commercial FastCGI installer for Windows.

Good luck, and speedy querying!


John Lim is based in Malaysia. He is the developer of ADOdb, a popular database library for PHP. John has been also been eating, drinking, and sleeping professionally with Oracle since 1995.

Return to DeployPHP Series index


Please rate this document:

Excellent Good Average Below Average Poor


Send us your comments

E-mail this page
Printer View Printer View
Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy