0

I have a web application calling a mysql database through vpn.

Sometimes (when a lot of customers are using the system) the cpu from the remote mysql host reaches 100% and everything goes so slow (I even get error 500).

I turned on slow queries log and nothing goes in there.

I saw the processlist and there are some queries (around 30 which is normal since I use a lot of ajax) that they are sorting data.

The query that does that is (with numbers filled in):

SELECT cs.id_client as id_client,  TIMESTAMPDIFF(SECOND,cs.call_start,cs.call_end) AS realduration, cs.*, cs.call_start + INTERVAL 0 HOUR as call_start_corrected, cs.call_end + INTERVAL 0 HOUR as call_end_corrected,`c`.`cost` AS `cscost` ,`c`.`call_rate` AS `ccall_rate`
FROM `callscallshop` AS `cs` LEFT JOIN
     `calls` AS `c`
     ON `c`.`call_start` = `cs`.`call_start`
WHERE `c`.`caller_id` = `cs`.`caller_id` AND 
      `cs`.`id_client` IN (9301) AND
      year(cs.call_start) = year(now()) AND 
      month(cs.call_start) = month(now()) AND 
      week(cs.call_start) = week(now()) AND
      `cs`.`invoice_id` = '-1'
ORDER BY cs.call_start DESC

I run the above on a local copy of the database and I get 0.0027 as time to execute.

I remove the time difference and the ordering and the difference time execution is so small..

On google chrome I see that the ajax response goes over 30s sometimes.

The task manager of the db server shows that the main load is mysql.exe

Does any of you have a clue to help me solve this?

Do you think is the query or something with the servers?

UPDATE

Ajax calls wait up to 30s every day in the afteroon!! It seems to be cpu usage that reaches 100%.

They do not allow to apply indexing on the server.

I will be grateful for any idea.

2 Answers2

2

Your LEFT JOIN is forced to be an INNER JOIN by the presence of this clause:

 WHERE c.caller_id = cs.caller_id

Write your ON clause like this instead and you'll fix that problem.

ON c.call_start = cs.call_start AND c.caller_id = cs.caller_id

The query you showed us can be optimized significantly, especially in the case that your calllscallshop table has a great many rows. Why? you are doing a date range search in an unsargeable way. Let's fix that.

You seem to be in a production emergency -- 500 messages and all -- so let's take things in order, easiest first.

First, create a compound covering index on the calllscallshop table. Let's see. Your query looks for equality on caller_id and invoice_id. It looks for set containment on id_client. It needs to do a range scan on call_start.

So, let's add this index to calllscallshop: (caller_id, invoice_id, call_start, id_client). Do that soon, and see if your problem improves. You can add the index without changing a line of software.

Also, add this index to calls: (caller_id, call_start)

Second, we need to do the this-week search much more efficiently. As a bonus, the change we make will fix a problem you have at the very beginning of each calendar year. You're doing it like this.

  year(cs.call_start) = year(now()) AND    /* slow */
  month(cs.call_start) = month(now()) AND  /* slow */
  week(cs.call_start) = week(now())        /* slow, wrong at year-end */

If you want this search to be sargeable, you need this instead:

    cs.call_start >= midnight on the first day of this week
AND cs.call_start <  midnight on the first day of next week

This way of writing the query makes MySQL able to range-scan search an index on call_start. It can be stunningly faster.

So the question is, how do we come up with midnight on the first day of this week?

If your weeks start on Sunday, this little formula does the trick.

 CURDATE()-INTERVAL DAYOFWEEK(CURDATE())-1 DAY

This works because DAYOFWEEK() returns Sunday=1, Monday=2, etc. So to get the preceding Sunday for any day, we back up DAYOFWEEK()-1 days.

So let's write your date range search like this:

    cs.call_start >= CURDATE()-INTERVAL DAYOFWEEK(CURDATE())-1 DAY
AND cs.call_start <  CURDATE()-INTERVAL DAYOFWEEK(CURDATE())-1 DAY + INTERVAL 7 DAY

That change should help your query a lot.

As bonus, you can do this

GROUP BY DATE(call_start)-INTERVAL DAYOFWEEK(DATE(call_start))-1 DAY 

to get weekly summaries of your data if you need them.

One more thing: When you're dealing with production data, doing SELECT * is considered harmful. Instead, you should specify the exact columns you need.

Community
  • 1
  • 1
O. Jones
  • 103,626
  • 17
  • 118
  • 172
  • thank you for the very clear and nice answer! I applied everything except indexing and in the new set of data I drop from 0.0039 to 0.0037. I will apply the indexing solution that you propose as well and see how it goes. I don't think though that this is the bottleneck :-( – JoomlaEvolved Development Sep 12 '15 at 13:52
  • The operating regime of a very busy production server is entirely different from what happens in a test server. – O. Jones Sep 12 '15 at 16:16
0

Ollies suggestions were very helpful but didn't solve the problem.

The problem was not in the sql directly, but in assosiation with the php code.

To be more clear:

  • each callshop has multiple calling cabins.
  • I was creating a connection and executing a query for each cabin.

What solved the problem was executing a query for multiple cabins and therefore having less connections and query executions per second.

The ajax call response dropped from 30 seconds to 850ms!

Thanks Ollie, your suggestions made my code a bit quicker and cleaner.