KrnlPanic's Linux Notes and Tips

Working with linux since kernel version 2.0.30

MySQL rand() High CPU Usage

MySQL has been eating my server alive for the past several months, and I never could determine the exact cause. I’m running MySQL in a VPS environment with 1GB of RAM with two dedicated AMD (Opteron) CPU cores. From a hardware standpoint, the system is a little bit dated, but has plenty of horsepower to run a fairly busy Apache/MySQL server.

So, after doing some analysis on my server setup and some digging around on various blogs and forums, I determined the problem. I had three separate php scripts that were pulling multiple random records out of a database. The MySQL queries looked something like this:

SELECT t1.field1, t1.field2, t2.field1, t2.field2
     FROM table1 t1, table2 t2 where t1.field1= '1' and t1.id = t2.id 
     ORDER BY RAND() LIMIT 10;

Now, the MySQL query above was selecting from two MySQL database tables with over 20,000 records each, then sorting and pulling out several random records. I knew at the time that this was a CPU-intensive way of going about it, but figured that I would optimize it someday. Unfortunately, someday never came until my server started having a difficult time serving all of the requests that it was getting.

I was seeing in the output of ‘top’ that CPU availability was bouncing against 0.0% and that some processes were even having to wait for CPU time with MySQL taking up to 70 or 75% of a single CPU. It looked something like this:

top - 20:36:59 up 492 days, 11:08,  1 user,  load average: 9.71, 8.47, 11.40
Tasks:  64 total,   1 running,  63 sleeping,   0 stopped,   0 zombie
Cpu(s):  3.0% us,  0.9% sy,  0.0% ni, 2.1% id,  1.2% wa,  0.0% hi,  0.0% si
Mem:   1048576k total,   487916k used,   360660k free,        0k buffers
Swap:        0k total,        0k used,        0k free,        0k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 1851 mysql     15   0  333m 222m 5000 S 72.3 26.8 464:48.28 mysqld
 1834 nobody    15   0 30752  16m 4324 S  3.0  1.6   0:04.53 httpd
 5841 nobody    15   0 28340  16m 3940 S  1.3  1.6   0:00.17 httpd
 5624 nobody    15   0 28240  16m 3976 S  1.0  1.6   0:00.40 httpd
 3805 nobody    15   0 30884  16m 4280 S  0.7  1.6   0:00.95 httpd
 5793 nobody    15   0 28120  16m 3972 S  0.7  1.6   0:00.28 httpd
 5798 nobody    15   0 27984  15m 3556 S  0.7  1.5   0:00.04 httpd

The problem was very noticeable on the web services side of things as well. Visiting a website on the server would sometimes result in 10 seconds or more of waiting for the httpd to serve a page after the connection was made. Sometimes, it would even reach the point where the browser would time out the connection.

Clearly, this was not a sustainable situation! So, I did some searching and digging, and eventually remembered those mysql queries that I had created over the last several months that I knew were CPU-intensive.

One of the alternative approaches that I saw relied upon the server-side php code generating a random number, then executing against that number instead of letting the MySQL rand() function do it. Since I really don’t care if all three of my return values from the database are truly random, I was more than happy to use the php value to create an index point into my set of records, then take the next 9 records in sequence to fill my 10 value quota.

// Get a random value between 1 and 10000 to create our index
$start_row = mt_rand(0, 10000);
// execute our query using limit $start_row, number_of 
$query = "SELECT t1.field1, t1.field2, t2.field1, t2.field2
     FROM table1 t1, table2 t2 where t1.field1 = '1' and t1.id = t2.id 
     ORDER BY t1.field2 ASC LIMIT $start_row, 10";

We could take this a step further and execute a separate query to get the actual number of records in the table instead of using the number 10000, but since I’m not concerned with getting to every single possible record in the table, I’m not too worried about that aspect. Maybe I’ll think about fixing/optimizing that part of it sometime in the next few months.