views:

262

answers:

1

Here's an excerpt of my current database (changed the table-names for an easier understanding):

Pet(ownerFK, id, name, age)
Owner(id, name)

Where id is always a surrogate key, created with auto_increment.

I want to have the surrogate key Pet.id to be "scoped" by Pet.ownerFK or in otherwords, have a composite key [ownerFk, id] as my minimum key. I want the table to behave like this:

INSERT Pet(1, ?, "Garfield", 8);
INSERT Pet(1, ?, "Pluto", 12);
INSERT Pet(2, ?, "Mortimer", 1);

SELECT * FROM Pet;
  RESULT:
   Pet(1, 1, "Garfield", 8)
   Pet(1, 2, "Pluto", 12)
   Pet(2, 1, "Mortimer", 1)

I am currently using this feature of MyISAM where "you can specify AUTO_INCREMENT on a secondary column in a multiple-column index. In this case, the generated value for the AUTO_INCREMENT column is calculated as MAX(auto_increment_column) + 1 WHERE prefix=given-prefix. This is useful when you want to put data into ordered groups."

However, due to various (and maybe obvious) reasons, I want to switch from MyISAM to InnoDB, as I need transactions at some places.

Is there any way how to achieve this effect with InnoDB?

I found some posts on this issue, many of them proposed to write-lock the table before insertion. I am not very familiar with this, but wouldn't be a table-write-lock a little-bit of an overhaul for this one? I rather thought of having write-safe transactions (which I never did before) if these are possible - having a Owner.current_pet_counter as an helper field.

So another acceptable Solution would be...

Actually I don't need the "scoped" ID to be part of the actual Key. My actual database design uses a separate "permalink" table which uses this 'feature'. I currently use it as a workaround for the missing transactions. I thought of the following alternative:

 Pet(id, ownerFK, scopedId, name, age), KEY(id), UNIQUE(ownerFK, scopedId)
 Owner(id, name, current_pet_counter)

 START TRANSACTION WITH CONSISTENT SNAPSHOT;
 SELECT @new=current_pet_counter FROM Owner WHERE id = :owner_id;
 INSERT Pet(?, :owner_id, @new, "Pluto", 21);
 UPDATE Owners SET current_pet_counter = @new + 1 WHERE id = :owner_id;
 COMMIT;

I haven't worked with transactions/transactionvars in MySQL yet, so I don't know whether there would be serious issues with this one. Note: I do not want to reuse ids that have been given to a pet once. That's why I don't use MAX(). Does this solution have any caveats?

+1  A: 

I don't believe so. If you really had to have that schema, you could use a transaction to SELECT the MAX(id) WHERE ownerFK, then INSERT.

I'm very sceptical there's a good reason for that schema, though; the primary key is now also a fact about the key, which might make the database theorists unhappy.

Normally you'd want ‘id’ to really be a proper primary key on its own, with ownerFK used to group and, if you needed it, a separate ‘rank’ column to put pets in a particular order per owner, and a UNIQUE index over (ownerFK, rank).

bobince
I see your point. My actual database design differs a little bit, and I also don't need it to be part of the primary key. I updated the question with the current solution I am evaluating.
Marcel J.