Ich habe mich über die Vor- und Nachteile der Verwendung einer UUID als Primärschlüssel in einer Datenbank informiert.
Das Hauptargument, das ich gegen eine solche Praxis gehört habe, ist, dass sie, wenn sie nicht sequentiell generiert werden, Ihre Indizes fragmentieren und Probleme mit dem Paging verursachen können (ich habe auch gehört, dass es die Größe Ihrer Datenbanken sprengt, aber lassen wir das erst einmal beiseite).
MSSQL Server erlaubt es Ihnen, sequentielle UUIDs innerhalb der Datenbank mit einer eigenen Methode zu erstellen (z.B. CREATE TABLE MyUniqueTable(UniqueColumn UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID()).
Das Problem dabei ist jedoch, dass eine nicht standardkonforme UUID erstellt wird, die nicht offensichtlich sequentiell ist. Ich habe das Format rückwärts entwickelt und es in einer Builder-Klasse zur Verwendung oder zum Studium gekapselt:
/**
* <p>
* Reverse engineering effort to replicate how SQL Server creates ordered
* UUIDs so that we may construct them within the application. The builder will
* only accept version 1 and version 14 (Microsoft specific) uuid objects as a
* seed.
* </p>
* <p>
* The algorithm is reversible so that a version 1 uuid may be created from a version
* 14 uuid and vice versa.
* </p>
* @author Michael Lambert
*
*/
public static class MsSqlOrderedUuidBuilder {
private static final TimeBasedGenerator generator = Generators.timeBasedGenerator();
private final UUID uuid;
public MsSqlOrderedUuidBuilder(UUID uuid) {
if(uuid.version() != 1 && uuid.version() != 14) {
throw new IllegalArgumentException(String.format("UUID is not a version 1 UUID (version is %d)", uuid.version()));
}
this.uuid = uuid;
}
public MsSqlOrderedUuidBuilder() {
this(generator.generate());
}
private long getMostSignificantBits() {
ByteBuffer buffer = ByteBuffer.wrap(new byte[8]);
buffer.putLong(uuid.getMostSignificantBits());
buffer.rewind();
byte[] timeLow = new byte[4];
buffer.get(timeLow);
byte[] timeMid = new byte[2];
buffer.get(timeMid);
byte[] timeHigh = new byte[2]; // time_high and version
buffer.get(timeHigh);
buffer.clear();
buffer.order(buffer.order().equals(ByteOrder.LITTLE_ENDIAN) ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buffer.put(timeHigh);
buffer.put(timeMid);
buffer.put(timeLow);
return buffer.getLong(0);
}
private long getLeastSignificantBits() {
return uuid.getLeastSignificantBits();
}
public UUID build() {
return new UUID(getMostSignificantBits(), getLeastSignificantBits());
}
}
Wenn ich versuche, diese Klasse zu verwenden, um die resultierenden UUIDs in einer anderen Datenbank zu speichern (ich bin auch in MySQL schreiben müssen) ist nicht am Ende wird bestellt und ich bin zurück zu meinem ursprünglichen Problem.
Meine Lösung bestand darin, meine eigene umkehrbare benutzerdefinierte UUID zu erstellen, die bei der Serialisierung in ein Byte-Array sequentiell geordnet ist:
/**
* <p>
* Creates a custom UUID type with sequential bytes. The builder must be seeded with a version 1 uuid and the
* algorithm is reversible.
* </p>
* @author Michael Lambert
*
*/
public static class SequentialUuidBuilder {
private static final TimeBasedGenerator generator = Generators.timeBasedGenerator();
private final UUID uuid;
public SequentialUuidBuilder(UUID uuid) {
if(uuid.version() != 1 && uuid.version() != 13) {
throw new IllegalArgumentException(String.format("UUID is not a version 1 UUID (version is %d)", uuid.version()));
}
this.uuid = uuid;
}
public SequentialUuidBuilder() {
this(generator.generate());
}
private long getVersion13MostSignificantBits() {
if(uuid.version() == 1) {
// System.out.println(String.format("original: %x", version1.getMostSignificantBits()));
//
// System.out.println(String.format("lowa %x", timeLowA));
//
// 0xAAAAA00000000000L
// 0x0000000AAAAA0000L
//
long timeLowPartA = (uuid.getMostSignificantBits() & 0xFFFFF00000000000L) >>> 28;
//
// 0x00000BBB00000000L
// 0x0000000000000BBBL
//
long timeLowPartB = (uuid.getMostSignificantBits() & 0x00000FFF00000000L) >>> 32;
//
// System.out.println(String.format("lowb %x", timeLowB));
//
// 0x00000000MMMM0000L
// 0x000MMMM000000000L
//
long timeMid = (uuid.getMostSignificantBits() & 0x00000000FFFF0000L) << 20;
//
// System.out.println(String.format("med %x", (timeMid)));
//
// 0x0000000000000HHHL
// 0xHHH0000000000000L
//
long timeHigh = (uuid.getMostSignificantBits() & 0x0000000000000FFFL) << 52;
//
// System.out.println(String.format("high %x", timeHigh));
//
// 0x000000000000V000L
// 0x000000000000V000L
//
// long version = (version1.getMostSignificantBits() & 0x000000000000F000L);
//
// System.out.println(String.format("version %x", version));
//
// 0x0000000AAAAA0000L
// 0x0000000000000BBBL
// 0x000MMMM000000000L
// 0xHHH0000000000000L
// 0x000000000000V000L <-- we don't change where the version is stored because we want to respect that part of the spec
// ____________________
// 0xHHHMMMMAAAAAVBBBL
//
long ordered = timeLowPartA | timeLowPartB | timeMid | timeHigh | 0x000000000000D000L; // custom version
return ordered;
}
return 0;
}
public long getVersion1MostSignificantBits() {
//
// 0xHHHMMMMAAAAAVBBBL
//
long timeLowPartA = (uuid.getMostSignificantBits() & 0x0000000FFFFF0000L) << 28;
long timeLowPartB = (uuid.getMostSignificantBits() & 0x0000000000000FFFL) << 32;
long timeMid = (uuid.getMostSignificantBits() & 0x000FFFF000000000L) >> 20;
long timeHigh = (uuid.getMostSignificantBits() & 0xFFF0000000000000L) >> 52;
//
// 0xAAAAA00000000000L
// 0x00000000MMMM0000L
// 0x00000BBB00000000L
// 0x0000000000000HHHL
// 0x000000000000V000L
// ___________________
// 0xAAAAABBBMMMMVHHHL
//
long bits = timeLowPartA | timeLowPartB | timeMid | timeHigh | 0x0000000000001000L; // reinstate version
return bits;
}
private long getMostSignificantBits() {
return (uuid.version() == 13) ? getVersion1MostSignificantBits() : getVersion13MostSignificantBits();
}
private long getLeastSignificantBits() {
return uuid.getLeastSignificantBits();
}
public UUID build() {
return new UUID(uuid.version() == 13 ? getVersion1MostSignificantBits() : getMostSignificantBits(), getLeastSignificantBits());
}
}
MEINE FRAGE lautet: Ist dies eine akzeptable Praxis? Kann ich BINARY(16) verwenden, um einen Primärschlüssel zu speichern, und ist es in Ordnung, einen benutzerdefinierten Bezeichner auf diese Weise zu verwenden?
Ich danke Ihnen allen im Voraus. Vive la Stackoverflow!