自定义 UUID 作为主键

发布于 2025-01-08 21:24:26 字数 6694 浏览 1 评论 0原文

我一直在阅读使用 UUID 作为数据库主键的优点和缺点。

我听到的反对这种做法的主要论点是,如果它们不是按顺序生成的,它们可能会使您的索引碎片化并造成分页问题(我也听说这会耗尽数据库的大小,但让我们先把它放在一边)现在)。

MSSQL Server 允许您使用自定义方法在数据库中创建顺序 UUID(例如 CREATE TABLE MyUniqueTable(UniqueColumn UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID())。

但它的问题是它创建了一个不符合标准的 UUID,显然不是顺序的。我有向后设计格式并将其封装在构建器类中以供使用或研究:

/**
 * <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());
    }
}

如果我尝试使用此类将生成的 UUID 存储在不同的数据库中(我还必须写信给 MySQL)最终不会被排序,我又回到了原来的问题。

我的解决方案是创建我自己的可逆自定义 UUID,当序列化为字节数组时,它是按顺序排序的:

/**
 * <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());
    }
}

我的问题是:这是可以接受的做法吗?我可以使用 BINARY(16) 来存储主键吗?可以用这种方式使用自定义标识符吗?

提前谢谢大家!

I have been reading on the advantages and disadvantages of using a UUID as a primar key in a database.

The main argument I have been hearing against such a practice is that if they aren't generated sequentially they can fragment your indexes and create problems with paging (I have also heard that is blows out the size of your databases but lets leave that aside for now).

MSSQL Server allows you to create sequential UUIDs within the database using a custom method (e.g. CREATE TABLE MyUniqueTable(UniqueColumn UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID()).

The problem with it though is that it creates a non standards compliant UUID that isnt obviously sequential. I have backward engineered the format and encapsulated it in a builder class for use or study:

/**
 * <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());
    }
}

If I attempt to use this class to store the resulting UUIDs in a different database (I am also have to write to MySQL) is doesnt end up being ordered and I am back to my original problem.

My solution was to create my own reversible custom UUID that when serialized into a byte array is sequentially ordered:

/**
 * <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());
    }
}

MY QUESTION IS: is this an acceptable practice? Can I use BINARY(16) to store a primary key and is it okay to use a custom identifier in this way?

Thank you all in advance. Vive la Stackoverflow!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

洒一地阳光 2025-01-15 21:24:26

使用序列生成器,除非您确实需要密钥具有普遍唯一性。

Use a sequence generator unless you truly need your keys to be universally unique.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文