I'm using Linode and I kept getting these I/O notifications...and the site would load extremely slow at times.
After looking at the log of my Rails app, I saw that some requests were taking > 20 seconds. I determined that this was some kind of blocking at the database.
I connected to MySQL and ran something simple...
select count(*) from location;
It took a long time...I turned profiling on and saw that it was taking a really long time in "table lock".
I assumed (which probably wasn't a good idea) that it was going through and counting all the rows.
I didn't really know what to make of this..because I didn't think that getting the count would take so long.
I embarked on reading about partitioning, which may have been a solution for a problem that didn't actually exist (based on the feedback here). I attempted to partition (on what I put in the article), and everything seemed much snappier after that. If the indexes should have solved the problem (given that they were correct), I don't know why the location queries were taking so long.
Anyway, I still have lots to learn on the database front, and maybe the fact that my VM had 7MB free of memory was causing weird things to happen, I'm not sure.
Thanks for all the feedback and I have definitely learned a lot in this thread.
kogir:
I'm using MyISAM (which, in retrospect, seems stupid), so I don't even have foreign key constraints (InnoDB only, I believe).
MySQL uses one key per query, so having an index on (device_id) and an index on (date_added) individually will do no (or very little) good for the queries you listed.
2. MyISAM uses table-level locking. This will block all reads (selects) while writes (insert, update, delete) are happening. It does not prevent multiple selects from happening at the same time. If you have a write-heavy table that you are also reading from you need to be using InnoDB.
3. SELECT COUNT(*) FROM location should be instantaneous in MyISAM . It will be slow(er) in InnoDB. If this query was blocking it's because something was trying to write to the table.
4. Foreign key constraints have nothing to do with anything here. That's one advantage of InnoDB, although most web apps I know don't use them, but not one that would help you. Rather, what will help you is the table-level vs. row-level for MyISAM vs. InnoDB.
This allows selects to happen to unaffected rows while writes are happening.
query_cache_limit = 1M query_cache_size = 16M key_buffer = 16M max_allowed_packet = 16M thread_stack = 192K thread_cache_size = 8
The key_len column in the EXPLAIN shows you how far it got through the index, based on the size of the columns. In your case it says 4 which if device id is of type INT it just read one column in the index (though there is probably only one column in that index)
If you have multiple single column indexes it won't help you here. The mysql query planner probably correctly guessed the table scan was a better option than hash joining the results of two seeks given the machine's limited memory. It also probably underestimated the cost of disk IO on a virtual machine though.
ActiveRecord is notorious for generating terrible, terrible SQL.
Edit: What I would expect to see would be index scan on device id, then sort + limit. So the important factor would be rows per device, not total rows.
Partitioning is a rather complex solution to his problem. See the limitations at http://dev.mysql.com/doc/refman/5.1/en/partitioning-limitati...
I can't know for sure since there's not enough information there, but I'd suspect a simple index would have done the trick.
CREATE INDEX my_index ON Location (device_id, date_added desc)
SELECT *
FROM Locations USE INDEX (my_index)
WHERE device_id = ...
ORDER BY date_added DESC
LIMIT 6
SELECT *
FROM Locations USE INDEX (my_index)
WHERE device_id = ...
AND date_added BETWEEN ... AND ...
ORDER BY date_added DESC
I'm assuming that he had the index and it just wasn't getting used, so the index hints are there in case the query planner was somehow missing them.That said, table partitioning is an awesome feature. I've seen it be most useful when you spread the partitions over tiered storage (latest on SSD, archive on spindles), or when you want to drop a whole range of data quickly on a regular basis (like last month's logs).
* Edit: Missed something:
You may need to specify more hints than I originally thought if you want to force index usage for the order by clause as well. See http://dev.mysql.com/doc/refman/5.1/en/index-hints.html
To make a covering index takes longer, so insertion speed drops, but that shouldn't be the hotspot.
To prevent going back to the table in this case, you'd either need the index I mentioned to be the primary key, or a construct like INCLUDE (which mysql appears to lack).