A deeper understanding on the matter could be helpful not just for finding a workaround to the error in question but also for better designing our tables and indexes. Some of the existing answers are good, but others are incomplete or clearly wrong, and that could be dangerous for hasty readers.
Important initial notes
- The question is about a
UNIQUE
index, but that's of little importance here. What matters is that the index of interest is compound (i.e. it consists of 2+ columns).
- In MySQL
KEY
is (in this context at least) a synonym to INDEX
.
The problem
In MySQL an index is required for every foreign key constraint (FKC). Not all DBMSs have such a requirement, but in practice, if you don't have such an index, some operations, such as deleting a record in a table that is referenced from other tables might suffer from terrible performance if those referencing tables are big enough. That's why with some RDBMs like MySQL, it's been a deliberate design decision to make such indexes required.
When we create a FKC, normally, MySQL automatically creates a new index with the same column(s) as the FK (remember, primary and foreign keys could consist of 2+ columns, but in OP's example they don't). However, if there already exists an index that could be used for the FKC, MySQL will not create another index.
In OP's example, if we don't count the primary key index, we have 3 indexes: AID
,BID
,CID
. The first one is compound, the others are not. Note that the name of the unique index might be a bit misleading, as it's the same as the name of the first column. I always recommend giving explicit names to compound indexes, to avoid potential confusion. So let's imagine the name of the UNIQUE KEY is ABC
and see what indexes the table would have at the starting point:
UNIQUE KEY `ABC` (`AID`,`BID`,`CID`),
KEY `BID` (`BID`),
KEY `CID` (`CID`),
The key ABC
can be used for the FKC on AID
, because AID
is the first column in it. That's why MySQL decided not to create an extra index on AID
only. But dropping an index that is internally used by MySQL to satisfy the requirement of always having an index, would be a no-no. Hence the error.
The solution
We should first ask ourselves: do we want to have an explicit dedicated index for AID
only, or do we want to continue using a compound index that we are having anyway. Both would be valid options, depending on our needs. If we want the best possible performance when quering on AID
only, having a separate index might be slightly more efficient, but that comes with the cost of having one more index (more storage, slower updates, etc.). More often than not, the pros of using the compound index outweigh the cons, but when performance is critical, it might be useful to at least be aware that there is an option to have a smaller dedicated index.
Option 1: Having a (permanent) dedicated index for the FKC
First, we create the dedicated index for the FKC:
CREATE INDEX IDX_AID ON mytable(AID);
Note that I've used IDX_AID
as a name here, but if we preferred to stick to the way MySQL names such indexes, AID
could also be used if available at this point. In my example with ABC
it would be, but in OP's case it wouldn't (although with RENAME INDEX
that could easily be solved).
Then we re-create the unique index with the new column (let's call it NEW_COL
and let's say we want it to be somewhere in the middle /remember, column order in indexes does matter/):
DROP INDEX ABC ON mytable;
CREATE UNIQUE INDEX IDX_ABNC ON mytable(AID, BID, NEW_COL, CID);
Now with SHOW CREATE TABLE mytable
we should be having:
UNIQUE KEY `IDX_ABNC` (`AID`,`BID`,`NEW_COL`,`CID`),
KEY `IDX_AID` (`AID`),
KEY `BID` (`BID`),
KEY `CID` (`CID`),
Option 2: No dedicated index for the FKC
We essentially need a workaround, to avoid the temporary inconsistent state.
Method 1: Temporary index on the FKC (preferred)
This is similar to Option 1 but we just drop the index at the end:
CREATE INDEX IDX_TMP ON mytable(AID);
DROP INDEX IDX_ABC ON mytable; -- here we have IDX_TMP, so no problem
CREATE UNIQUE INDEX IDX_ABNC ON mytable(AID, BID, NEW_COL, CID);
DROP INDEX IDX_TMP ON mytable; -- not needed anymore
Method 2: Drop FKC and then recreate it
Essentially, the idea is to first drop the "guard" (i.e. the FKC) temporarily, and recreate it at the end.
ALTER TABLE mytable DROP FOREIGN KEY <FKC-name>;
DROP INDEX IDX_ABC ON mytable; -- no "guard" here to stop us
CREATE UNIQUE INDEX IDX_ABNC ON mytable(AID, BID, NEW_COL, CID);
ALTER TABLE mytable ADD CONSTRAINT <FKC-name> FOREIGN KEY (AID) REFERENCES mytable(AID) ON DELETE CASCADE;
You can find the <FKC-name>
from SHOW CREATE TABLE mytable
.
Recommendation
I think Method 1 is preferable, for the following reasons:
- A bit simpler (no need to look up for the exact FKC name).
- Dropping the FKC, even if for a very short time, opens the window for someone inserting an invalid value.
- There is a potential risk to mix something up when recreating the FKC. For example, you might forget to add an important attribute (such as
ON DELETE CASCADE
). Or, to accidentally put a wrong one.
- If, for some reason, we forgot or failed to execute the last step, with Method 1 no harm is done, whereas with Method 2 we've left the DB in a vulnerable state.