Update index values extremely slow on MySQL - mysql

I have three tables, one is in database db1 and two are in database db2, all on the same MySQL server:
CREATE TABLE `db1`.`user` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_name` varchar(20) NOT NULL,
`password_hash` varchar(71) DEFAULT NULL,
`email_address` varchar(100) NOT NULL,
`registration_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`registration_hash` char(16) DEFAULT NULL,
`active` bit(1) NOT NULL DEFAULT b'0',
`public` bit(1) NOT NULL DEFAULT b'0',
`show_name` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY (`id`),
UNIQUE KEY `user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `db2`.`ref` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `db2`.`combination` (
`ref_id` bigint(20) UNSIGNED NOT NULL,
`user_id` bigint(20) UNSIGNED NOT NULL,
`arbitrary_number` tinyint(3) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`figurine_id`,`user_id`),
KEY `combination_user` (`user_id`),
KEY `combination_number` (`user_id`,`arbitrary_number`),
CONSTRAINT `combination_ref` FOREIGN KEY (`ref_id`) REFERENCES `ref` (`id`) ON UPDATE CASCADE,
CONSTRAINT `combination_user` FOREIGN KEY (`user_id`) REFERENCES `db1`.`user` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The table db1.user has around 600 records, the table db2.ref has around 800 records and the table db2.combination has around 300K records.
Now using Perl and DBD::mysql I perform the following query:
UPDATE `db1`.`user` SET `id` = (`id` + 1000)
ORDER BY `id` DESC
However, this query always stops mentioning the connection to the MySQL server was lost. Also executing this same query via PhpMyAdmin results in a timeout. Somehow the query just takes a very long time to execute. I guess it comes because all the foreign key values need to be updated.
Setting FOREIGN_KEY_CHECKS variable to OFF will not update the user_id column in the db.combination table, which do need to be updated.
I have also tried to manipulate the different timeouts (as suggested all over the internet), like this:
SET SESSION net_read_timeout=3000;
SET SESSION net_write_timeout=3000;
SET SESSION wait_timeout=6000;
I have verified that the new values are actually set, by retrieving the values again. However, even with these long timeouts, the query still fails to execute and after about 30 seconds the connection to the MySQL server is again lost (while amidst executing the UPDATE query)
Any suggestions on how to speed up this query are more than welcome.
BTW: The PK columns have a very large integer type. I will also make this type smaller (change to INT). Could this type change also improve the speed significantly?
UPDATE
I also performed an EXPLAIN for the query and it mentions in the Extra column that the query is doing a filesort. I would have expected that due to the indexes on the table (added them, as they were not there in the first place), no filesort would take place.

The 300K CASCADEs is probably the really slow part of the task. So, let's avoid it. (However, there may be a check the verify the resulting links; this should be not-too-slow.)
Disable FOREIGN KEY processing
Create new tables without FOREIGN KEYs. new_user, new_combination. (I don't know if new_ref is needed.)
Do this to populate the tables:
INSERT INTO new_xx (user_id, ...)
SELECT user_id + 1000, ...;
ALTER TABLE new_xx ADD FOREIGN KEY ...; (for each xx)
`RENAME TABLE xx TO old_xx, new_xx TO xx;
`DROP TABLE old_xx;
Enable FOREIGN KEY processing

Related

MySQL (Percona) Error 1452: Cannot add or update a child row for no reason

I have 2 Database Tables "users" and "tasks".
The tasks table contains two foreign keys for the createdBy_user_id and updatedBy_user_id columns both referencing users.id. If I try to insert an entry to tasks (after making sure the user referenced by the foreign keys exists) like this:
INSERT INTO tasks (createdBy_user_id,updatedBy_user_id,noOfYear,createdAt,updatedAt,status,customer_id)
VALUES (1,1,1,NOW(),NOW(),"open",1)
The query fails with Error 1452:
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`tasks`, CONSTRAINT `user_id_fk_constr` FOREIGN KEY (`createdBy_user_id`) REFERENCES `users` (`id`))
I don't know why this happens, because everything works fine if I remove the constraint. The same error does not happen for the "updatedBy_user_id" column making this such confusing.
The Tables have the following DDL:
Users table:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`active` tinyint(1) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`email_confirmed_at` datetime DEFAULT NULL,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`first_name` varchar(50) DEFAULT NULL,
`last_name` varchar(50) DEFAULT NULL,
`job` varchar(64) DEFAULT NULL,
`position` varchar(64) DEFAULT NULL,
`specialKnowledge` text,
`tasks` text,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
Tasks table:
CREATE TABLE `tasks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`noOfYear` int(11) NOT NULL,
`year` int(11) GENERATED ALWAYS AS (right(year(`createdAt`),2)) VIRTUAL NOT NULL,
`createdBy_user_id` int(11) NOT NULL,
`updatedBy_user_id` int(11) NOT NULL,
`status` enum('open','closed') NOT NULL,
`customer_id` int(11) NOT NULL,
`projectDescription` text,
PRIMARY KEY (`id`),
UNIQUE KEY `tasks_year_unique_constr` (`year`,`noOfYear`),
KEY `user_id_fk_constr` (`createdBy_user_id`),
KEY `customer_id_fk_constr` (`customer_id`),
KEY `user_up_id_fk_constr` (`updatedBy_user_id`),
CONSTRAINT `customer_id_fk_constr` FOREIGN KEY (`customer_id`) REFERENCES `Customer` (`id`),
CONSTRAINT `user_id_fk_constr` FOREIGN KEY (`createdBy_user_id`) REFERENCES `users` (`id`),
CONSTRAINT `user_up_id_fk_constr` FOREIGN KEY (`updatedBy_user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
As you can see the datatypes match and both tables use the InnoDB Engine.
The users table contains one entry:
select id,username from users;
+----+----------+
| id | username |
+----+----------+
| 1 | admin |
+----+----------+
1 row in set (0.00 sec)
So there is no obvious reason, why the insertion should fail. Have you an idea what's wrong with my tables? Looks like a software bug.
This is a bug. MySQL 5.7 has had some troubles with generated columns and foreign keys, and I assume this is an unfixed variant of Bug #79772 Foreign key not allowed when a virtual index exists.
In your particular case, and probably depending on your exact version, any of the following modifications seems to prevent that bug from occurring:
do not use a virtual column, but make it stored
do not create a foreign key for a column directly following the virtual column, so e.g. change the column order to year, status, createdBy_user_id, updatedBy_user_id.
do not use a unique index on the virtual column, a normal index should be fine (at least in a version where the linked bug is fixed). You want a unique constraint, so this is not an option, but the fact that this fixes your problem emphasized the "bug" nature of the problem.
The second bullet point seems to be the underlying bug: I assume that some iterator doesn't count the virtual column properly, so the foreign key that shall check createdBy_user_id seems to mix up the columns and actually checks the value of year (in this case "20") against the users table. So if you have a user with id "20" in your users table, the foreign key will actually accept this, no matter what value for createdBy_user_id you are trying to insert, see the MySQL 5.7.29 fiddle.
Unless you have a specific reason to use a virtual column, using stored is probably the sane thing to do.

How to break find duplicate SQL query on a large table in multiple parts

I have a large table with ~3 million records in a MySQL database. I am trying to find duplicate rows in this table using the following query -
SELECT package_id
FROM version
WHERE metadata IS NOT NULL AND metadata <> '{}'
GROUP BY package_id, metadata HAVING COUNT(package_id) > 1
This query takes ~23 seconds to run on the database. Our database host however kill any query taking larger than 3 seconds using pt-kill. So I need to find a way to break this query down, such as each of the subpart would be a separate query and each one takes less than 3 seconds. Adding just a LIMIT constraint doesn't do it for the query, so how do I break a query to work on different parts of the table.
Result of SHOW CREATE TABLE version
CREATE TABLE `version` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`package_id` bigint(20) unsigned NOT NULL,
`version_number` int(11) unsigned NOT NULL,
`current_state_id` tinyint(2) unsigned NOT NULL,
`md5sum` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_cs NOT NULL DEFAULT '',
`uri` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_cs NOT NULL DEFAULT '',
`filename` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_cs NOT NULL DEFAULT '',
`size` bigint(11) unsigned NOT NULL DEFAULT '0',
`metadata` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_cs DEFAULT NULL,
`storage_type_id` tinyint(2) unsigned NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_version_package_id_version_number` (`package_id`,`version_number`),
KEY `idx_version_md5sum` (`md5sum`),
KEY `idx_version_metadata` (`metadata`(255)),
KEY `idx_version_current_state_id` (`current_state_id`),
KEY `storage_type_id` (`storage_type_id`),
CONSTRAINT `_fk_version_current_state_id` FOREIGN KEY (`current_state_id`) REFERENCES `state` (`id`),
CONSTRAINT `_fk_version_package_id` FOREIGN KEY (`package_id`) REFERENCES `package` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3248761 DEFAULT CHARSET=utf8
As can be seen there are many indexes on the table including index on Package_id + Version_number combination of field. The problem is that this table is only going to get bigger and I don't think Optimization even if it pulls me back in 3 second range would scale. So I need a way where I can partition this table and run on queries on separate parts.
Steps to improve speed.
Create Table version_small with just columns id and package_id with index on package_id.
insert into version_small select id and package_id from version;
Run your original query on optimised table above - should be much faster on smaller table.
OR
Create Table version_small with just columns id and package_id, and a int counter with unique index on package_id.
insert into version_small select id and package_id from version, on duplicate key increment counter;
The rows with counter>1 are package_id that have more than one entry.

Error when creating foreign key of type CHAR with mysql workbench: Error 1005: Can't create table (errno: 150)

I have defined the following 2 tables:
record_status
SHOW CREATE TABLE record_status
CREATE TABLE `record_status` (
`record_status_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`status` char(6) NOT NULL,
`status_description` varchar(15) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`record_status_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
user
SHOW CREATE TABLE user
CREATE TABLE `user` (
`user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`handle` varchar(45) NOT NULL,
`email` varchar(255) NOT NULL,
`password` char(64) DEFAULT NULL,
`password_salt` binary(1) DEFAULT NULL,
`first_name` varchar(50) NOT NULL,
`last_name` varchar(50) NOT NULL,
`gender` char(1) DEFAULT NULL,
`birthday` date NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`user_status` char(6) DEFAULT NULL,
PRIMARY KEY (`user_id`),
KEY `usr_status_idx` (`user_status`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
and I tried adding the foreign key user_status of type CHAR using mysql Workbench as follows:
ALTER TABLE `mydatabase`.`user`
ADD CONSTRAINT `usr_status`
FOREIGN KEY (`user_status`)
REFERENCES `mydatabase`.`record_status` (`status`)
ON DELETE NO ACTION
ON UPDATE NO ACTION;
but I am getting the following error:
Error:
Executing SQL script in server
ERROR: Error 1005: Can't create table 'mydatabase.#sql-420_1b0' (errno: 150)
ALTER TABLE 'mydatabase'.'user'
ADD CONSTRAINT 'usr_status'
FOREIGN KEY ('user_status')
REFERENCES 'mydatabase'.'record_status'('status')
ON DELETE NO ACTION
ON UPDATE NO ACTION
SQL script execution finished: statements: 4 succeeded, 1 failed.
Question
My intention is to have the status column clearly show the current status for each user (ACTIVE, INACTV, DELETD) while still having the flexibility to join the record_status table with the user table using the record_status_id to find any rows with a given status for better performance.
I found a similar post here
Adding foreign key of type char in mysql
which suggests to change my primary key's collation but, how would that affect my user table?
Will I have to change the collation to the user_status field in my user table as well? The user table will be queried every time a user logs in and I am concerned about performance or any constraints this may cause.
I also intend to add a foreign key for the status to a few other tables as well. I would just like to know how this affects performance, or does it add any constraints?
Any input regarding my design will also be appreciated. Thank you for your help!
The issue you're facing isn't actually related to collation (though collation can be a cause of the error you're experiencing under different circumstances).
Your FOREIGN KEY constraint is failing because you don't have an index individually on record_status.status. You have that column as part of the composite PRIMARY KEY (record_status_id, status), but for successful foreign key constraint creation, both the referencing table and the referenced table must have indexes on exactly the columns used in the key relationship (in addition to the same data types).
Adding the FOREIGN KEY constraint implicitly creates the necessary index on the referencing table, but you must still ensure you have the corresponding index on the referenced table.
So given what you have now, if you added a single index on record_status.status, the constraint would correctly be created.
CREATE TABLE `record_status` (
`record_status_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`status` char(6) NOT NULL,
`status_description` varchar(15) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`record_status_id`,`status`),
-- This would make your relationship work...
KEY (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
However, I don't think that's the best course of action. I don't see a need for the composite primary key on (record_status_id, status), chiefly because the record_status_id is itself AUTO_INCREMENT and guaranteed to be unique. That column alone could be the PRIMARY KEY, while still adding an additional UNIQUE KEY on status to satisfy the foreign key constraint's indexing requirement. After all, it is not the combination of record_status_id and status which uniquely identifies each row (making a primary key)
CREATE TABLE `record_status` (
`record_status_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`status` char(6) NOT NULL,
`status_description` varchar(15) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Primary only on record_status_id
PRIMARY KEY (`record_status_id`),
-- Additional UNIQUE index on status
UNIQUE KEY (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
About the design -- eliminating record_status_id...
Without knowing how the rest of your application currently uses record_status_id, I can't say for sure if it required by your application code. But, if you wish to make the actual status value easily available to other tables, and it is merely CHAR(6), it is possible that you actually have no need for record_status_id as an integer value. After all, if the status string is intended to be unique, then it is perfectly capable of serving as the PRIMARY KEY on its own, without any auto-increment integer key.
In that case, your record_status table would look like below, and your FOREIGN KEY constraint would correctly be added to users.
CREATE TABLE `record_status` (
-- Remove the auto_increment column!!
`status` char(6) NOT NULL,
`status_description` varchar(15) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Status is unique, and therefore can be the PK on its own
PRIMARY KEY (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
Given this setup, here's a sample showing the successful creation of the tables and addition of the FK constraint.
You asked about performance implications of adding a status FK to other tables as well. It's tough to speculate on that without knowing the purpose, but if other tables share the same status values, then it makes sense to create their FK constraints to link to it in the same say you're doing with users. And if that's the case, I would recommend doing it the same way, wherein the status column is CHAR(6) (or consider changing all of them to VARCHAR(6)). The value of record_status.status still makes sense as the true primary key, and can be used as the FK in as many related tables as necessary.
In all but the most gigantic scale, there should be no appreciable performance difference between using an INT value and a CHAR(6)/VARCHAR(6) value as the foreign key. And the storage size difference between them is equally tiny. It isn't worth worrying about unless you must scale this to positively enormous proportions.

mysql won't allow foreign key

Many people had this problem already, but there was no fitting solution in other posts.
I have two tables, one named "sales", the other named "host_flags". I would like to have a foreign key for host_flags.sales_id to sales.id, but mysql won't let me! I have primary indexes defined in each table, so I wonder why...
The host_flags table already has a foreign key on the column host_id, but even when I tried and created the foreign key for the sales id first, it wouldn't let me.
The tables look like:
CREATE TABLE `sales` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`creation` datetime DEFAULT NULL,
`lastupdate` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
CREATE TABLE `host_flags` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`host_id` int(11) DEFAULT NULL,
`sales_id` int(11) DEFAULT NULL,
`creation` datetime DEFAULT NULL,
`lastupdate` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `host_id6` (`host_id`),
CONSTRAINT `host_id6` FOREIGN KEY (`host_id`) REFERENCES `hosts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `hosts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`creation` datetime NOT NULL,
`lastupdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32225 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
I get this error message:
MySQL said: Can't create table 'primarydata.#sql-191_1' (errno: 150)
Thanks!
Charles
SOLUTION FOUND
All ints of the primary indexes have to be either signed or unsigned - not mixed.
Typically:
I like to declare the FK constraints outside of the table definition after all tables have been constructed.
ALTER TABLE `tbl`
ADD CONSTRAINT `constr`
FOREIGN KEY `fk_id` REFERENCES `ftbl`(`id`)
ON UPDATE CASCADE
ON DELETE CASCADE;
This way I can make sure the problem isn't something like the datatype of tbl.fk_id not being the same as the one of ftbl.id (including UNSIGNED as #Devart said). Or not having declared ftbl.id as unique. Regardless of the order of declaration of the tables.
After i do this i can integrate the constraint back into the table definition and take into account the order in which the tables need to be created to allow the constraint to be added.
You problem:
-- creating the sales table
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-- creating the host_flags table
`sales_id` int(11) DEFAULT NULL,
-- the sales.id is declared as unsigned
-- the host_flags.sales_id is declared signed
Additonally to Recursed's answer:
There is a requirement that foreign keys contstraints' names must be unique in database scope. Maybe changing the name will work?
There is also a huge thread on MySQL community forums about the problem containing several solutions for some specific situations.
Possible two errors:
Reference and referenced columns must have the same type - int(11) unsigned
Unknown referenced table hosts.

Really Slow MySQL Insert Query

I've got a table with about half a million records in it. It's not huge. A couple varchar(255) fields, some ints, a float, and a couple timestamps. There are indices on the ints as well as foreign key constraints. Inserts are taking forever. I'm talking 1-4 seconds to insert one row. I've had to deal with slow select queries plenty of times, but I'm stuck trying to figure out what's going on with this insert.
EDIT: Okay, I was really just asking for ideas on how to debug this, but, here's all the tables involved. Inserting into "ingredients" is what takes forever. Hopefully throwing a good portion of my schema onto the web doesn't bite me later...
CREATE TABLE `ingredients` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`quantity` float DEFAULT NULL,
`food` varchar(255) NOT NULL,
`unit_id` int(11) DEFAULT NULL,
`ingredient_group_id` int(11) DEFAULT NULL,
`order_by` int(11) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`description` varchar(255) DEFAULT NULL,
`range` float DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `unit_id` (`unit_id`),
KEY `ingredient_group_id` (`ingredient_group_id`),
CONSTRAINT `ingredients_ibfk_1` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`),
CONSTRAINT `ingredients_ibfk_2` FOREIGN KEY (`ingredient_group_id`) REFERENCES `ingredient_groups` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=269974 DEFAULT CHARSET=utf8
CREATE TABLE `units` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`abbreviation` varchar(255) CHARACTER SET latin1 NOT NULL,
`type` int(11) NOT NULL,
`si` float NOT NULL,
`lower_bound` float DEFAULT NULL,
`lower_unit_id` int(11) DEFAULT NULL,
`upper_bound` float DEFAULT NULL,
`upper_unit_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `lower_unit_id` (`lower_unit_id`),
KEY `upper_unit_id` (`upper_unit_id`),
CONSTRAINT `units_ibfk_1` FOREIGN KEY (`lower_unit_id`) REFERENCES `units` (`id`),
CONSTRAINT `units_ibfk_2` FOREIGN KEY (`upper_unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8
CREATE TABLE `ingredient_groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`recipe_id` int(11) NOT NULL,
`order_by` int(11) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `recipe_id` (`recipe_id`),
CONSTRAINT `ingredient_groups_ibfk_1` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=32739 DEFAULT CHARSET=utf8
lots of information missing, but the things i'd check first:
if on MyISAM tables: extremely fragmented files, especially index files. Use filefrag to check that. This can happen if the database grew slowly with time. If so, just shut down MySQL, copy the database directory, rename the original and new copies and restart MySQL
if you use InnoDB tables: a file-based datastore, again too fragmented. In this case, fragmentation can be both at filesystem level (check and handle as above) or at datastore level, for that use the InnoDB tools. In the worst case a block-device-based datastore (which can't get externally fragmented) can exhibit a bad case of internal fragmentation.
some index with extremely low cardinality. That is, a non-unique index with few distinct values present, that is, lots of repeats. This indexes approach asymptotically a linear list, with O(n) time profiles. This can be either an index on the table or the referred foreign index.
reader contention. unlikely, but a huge number of concurrent readers can stall a single writer.
Edit:
After reading your definitions, i think ingredients.unit_id and ingredients.ingredient_group_id are the first candidates to check, since they seem to have very low cardinality.
The first one is unlikely to be useful (do you plan to select all ingredients that are measured in spoons?), so you can probably just drop it.
The second one can be very useful; but if there are few ingredient groups, the cardinality can be very low, degrading performance. To raise cardinality, add some part to make it more discriminating. If no other field is likely to appear in a query together with group id, just add the main id or creation date, making it (ingredient_group_id, id) or (ingredient_group_id, created_at). Seems counterintuitive to add complexity to make it faster, but it can really help. As a bonus, you can add a sort by created_at to any query that selects by ingredient_group_id without performance penalty.
You might want to look at the ingredients.unit_id index, since it has a low selectivity.
are the inserts happening concurrently?
Turns out I had a trigger that was falling prey to this bug:
http://bugs.mysql.com/bug.php?id=9021
I turned it from an IN to an = and now inserts are running in 0.00 seconds.
I totally forgot I had a trigger hooked up to this table. That's my fault. Sorry to anyone who wasted their time trying to help me out, but thank you so much anyway.