MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?

Hãy tưởng tượng có một công cụ có thể tự động phát hiện các vấn đề về hiệu suất của JPA và Hibernate. Điều đó sẽ không tuyệt vời sao?

Chà, Hypersistence Optimizer là công cụ đó. Và nó hoạt động với Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus hoặc Play Framework

Vì vậy, hãy tận hưởng thời gian dành cho những thứ bạn yêu thích hơn là khắc phục các vấn đề về hiệu suất trong hệ thống sản xuất của bạn vào tối thứ Bảy

Bạn có thể kiếm được nguồn thu nhập thụ động đáng kể từ việc quảng bá sách, khóa học, công cụ, đào tạo hoặc đăng ký huấn luyện của tôi

Nếu bạn quan tâm đến việc bổ sung thu nhập của mình, thì hãy tham gia chương trình liên kết của tôi

Giới thiệu

Trong bài viết này, chúng ta sẽ xem loại UUID (Mã định danh duy nhất toàn cầu) nào hoạt động tốt nhất cho cột cơ sở dữ liệu có ràng buộc Khóa chính

Mặc dù UUID ngẫu nhiên 128 bit tiêu chuẩn là một lựa chọn rất phổ biến, nhưng bạn sẽ thấy rằng đây là một sự phù hợp khủng khiếp đối với cột Khóa chính của cơ sở dữ liệu

UUID tiêu chuẩn và khóa chính cơ sở dữ liệu

Mã định danh duy nhất toàn cầu (UUID) là chuỗi giả ngẫu nhiên 128 bit có thể được tạo độc lập mà không cần một hệ thống tập trung duy nhất chịu trách nhiệm đảm bảo tính duy nhất của mã định danh

Thông số kỹ thuật RFC 4122 xác định, được triển khai bởi các chức năng cơ sở dữ liệu hoặc ngôn ngữ lập trình khác nhau

Chẳng hạn, hàm MySQL trả về số UUID phiên bản 1

Và hàm Java trả về số UUID phiên bản 4

Đối với nhiều nhà phát triển, việc sử dụng các UUID tiêu chuẩn này làm mã định danh cơ sở dữ liệu rất hấp dẫn vì

  • Các id có thể được tạo bởi ứng dụng. Do đó không cần điều phối trung tâm
  • Cơ hội va chạm định danh là cực kỳ thấp
  • Giá trị id là ngẫu nhiên, bạn có thể gửi nó đến giao diện người dùng một cách an toàn vì người dùng sẽ không thể đoán các giá trị định danh khác và sử dụng chúng để xem dữ liệu của người khác

Tuy nhiên, sử dụng một UUID ngẫu nhiên làm khóa chính của bảng cơ sở dữ liệu là một ý tưởng tồi vì nhiều lý do

Đầu tiên, UUID rất lớn. Mỗi bản ghi sẽ cần 16 byte cho mã định danh cơ sở dữ liệu và điều này cũng ảnh hưởng đến tất cả các cột Khóa ngoại được liên kết

Thứ hai, cột Khóa chính thường có chỉ mục B+Tree được liên kết để tăng tốc độ tra cứu hoặc tham gia và chỉ mục B+Tree lưu trữ dữ liệu theo thứ tự được sắp xếp

Tuy nhiên, lập chỉ mục các giá trị ngẫu nhiên bằng B+Tree gây ra nhiều vấn đề

  • Các trang chỉ mục sẽ có hệ số lấp đầy rất thấp vì các giá trị đến ngẫu nhiên. Vì vậy, một trang 8kB cuối cùng sẽ chỉ lưu trữ một vài phần tử, do đó lãng phí rất nhiều dung lượng, cả trên đĩa và trong bộ nhớ cơ sở dữ liệu, vì các trang chỉ mục có thể được lưu vào bộ đệm trong Vùng đệm
  • Bởi vì chỉ mục B+Tree cần tự cân bằng lại để duy trì cấu trúc cây cách đều nhau, nên các giá trị khóa ngẫu nhiên sẽ khiến nhiều trang chỉ mục bị phân tách và hợp nhất vì không có thứ tự xác định trước để lấp đầy cấu trúc cây

Nếu bạn đang sử dụng SQL Server hoặc MySQL, thì điều đó thậm chí còn tệ hơn vì toàn bộ bảng về cơ bản là một chỉ mục được nhóm

MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?

Và tất cả những vấn đề này cũng sẽ ảnh hưởng đến các chỉ mục phụ vì chúng lưu trữ giá trị Khóa chính trong các nút lá chỉ mục phụ

MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?

Trên thực tế, hầu hết mọi chuyên gia về cơ sở dữ liệu sẽ yêu cầu bạn tránh sử dụng các UUID tiêu chuẩn làm bảng cơ sở dữ liệu Khóa chính

  • Percona. UUID phổ biến, nhưng không tốt cho hiệu suất
  • UUID hoặc GUID làm Khóa chính?
  • Cuộc khủng hoảng bản sắc. trình tự v. UUID làm Khóa chính

TSID – Mã định danh duy nhất được sắp xếp theo thời gian

Nếu bạn dự định lưu trữ các giá trị UUID trong cột Khóa chính, thì tốt hơn hết bạn nên sử dụng TSID (số nhận dạng duy nhất được sắp xếp theo thời gian)

Một triển khai như vậy được cung cấp bởi thư viện TSID Creator OSS, cung cấp TSID 64 bit được tạo thành từ hai phần

  • một thành phần thời gian 42-bit
  • một thành phần ngẫu nhiên 22-bit

Thành phần ngẫu nhiên có hai phần

  • một mã định danh nút (0 đến 20 bit)
  • một bộ đếm (2 đến 22 bit)

Mã định danh nút có thể được cung cấp bởi thuộc tính hệ thống

export TSIDCREATOR_NODE="12"
6 khi khởi động ứng dụng

-Dtsidcreator.node="12"

Định danh nút cũng có thể được cung cấp thông qua biến môi trường

export TSIDCREATOR_NODE="12"
7

export TSIDCREATOR_NODE="12"

Thư viện có sẵn trên Maven Central, vì vậy bạn có thể lấy nó thông qua phần phụ thuộc sau


    com.github.f4b6a3
    tsid-creator
    ${tsid-creator.version}

Bạn có thể tạo một đối tượng

export TSIDCREATOR_NODE="12"
8 có thể sử dụng tối đa 256 nút như thế này

Tsid tsid = TsidCreator.getTsid256();

Từ đối tượng

export TSIDCREATOR_NODE="12"
8, chúng ta có thể trích xuất các giá trị sau

  • giá trị số 64 bit,
  • giá trị mã hóa giá trị 64-bit,
  • mili giây Unix kể từ kỷ nguyên được lưu trữ trong chuỗi
    
        com.github.f4b6a3
        tsid-creator
        ${tsid-creator.version}
    
    
    0-bit

Để trực quan hóa các giá trị này, chúng ta có thể in chúng vào nhật ký

long tsidLong = tsid.toLong();
String tsidString = tsid.toString();
long tsidMillis = tsid.getUnixMilliseconds();

LOGGER.info(
    "TSID numerical value: {}", 
    tsidLong
);

LOGGER.info(
    "TSID string value: {}", 
    tsidString
);

LOGGER.info(
    "TSID time millis since epoch value: {}", 
    tsidMillis
);

Và chúng tôi nhận được đầu ra sau

TSID numerical value: 388400145978465528
TSID string value: 0ARYZVZXW377R
TSID time millis since epoch value: 1670438610927

Khi tạo ra mười giá trị

for (int i = 0; i < 10; i++) {
    LOGGER.info(
        "TSID numerical value: {}",
        TsidCreator.getTsid256().toLong()
    );
}

Chúng ta có thể thấy rằng các giá trị đang tăng đơn điệu

TSID numerical value: 388401207189971936
TSID numerical value: 388401207189971937
TSID numerical value: 388401207194165637
TSID numerical value: 388401207194165638
TSID numerical value: 388401207194165639
TSID numerical value: 388401207194165640
TSID numerical value: 388401207194165641
TSID numerical value: 388401207194165642
TSID numerical value: 388401207194165643
TSID numerical value: 388401207194165644

Tuyệt vời, phải không?

Sử dụng TSID trong ứng dụng của bạn

Bởi vì các nhà máy TSID mặc định được cung cấp thông qua tiện ích


    com.github.f4b6a3
    tsid-creator
    ${tsid-creator.version}

1 đi kèm với bộ tạo giá trị ngẫu nhiên được đồng bộ hóa, nên tốt hơn là sử dụng một

    com.github.f4b6a3
    tsid-creator
    ${tsid-creator.version}

2 tùy chỉnh cung cấp các tối ưu hóa sau

  • Nó có thể tạo các giá trị ngẫu nhiên bằng cách sử dụng
    
        com.github.f4b6a3
        tsid-creator
        ${tsid-creator.version}
    
    
    3, do đó tránh chặn Chủ đề trên các khối được đồng bộ hóa
  • Nó có thể sử dụng một số lượng nhỏ bit nút, do đó để lại cho chúng tôi nhiều bit hơn cho giá trị số được tạo ngẫu nhiên

Vì vậy, chúng ta có thể định nghĩa


    com.github.f4b6a3
    tsid-creator
    ${tsid-creator.version}

4 sau đây cung cấp cho chúng ta một

    com.github.f4b6a3
    tsid-creator
    ${tsid-creator.version}

2 để sử dụng bất cứ khi nào chúng ta muốn tạo một đối tượng
export TSIDCREATOR_NODE="12"
8 mới

public static class TsidUtil {
    public static final String TSID_NODE_COUNT_PROPERTY = 
        "tsid.node.count";
    public static final String TSID_NODE_COUNT_ENV = 
        "TSID_NODE_COUNT";

    public static TsidFactory TSID_FACTORY;

    static {
        String nodeCountSetting = System.getProperty(
            TSID_NODE_COUNT_PROPERTY
        );
        if(nodeCountSetting == null) {
            nodeCountSetting = System.getenv(
                TSID_NODE_COUNT_ENV
            );
        }

        int nodeCount = nodeCountSetting != null ?
            Integer.parseInt(nodeCountSetting) :
            256;

        int nodeBits = (int) (Math.log(nodeCount) / Math.log(2));

        TSID_FACTORY = TsidFactory.builder()
            .withRandomFunction(length -> {
                final byte[] bytes = new byte[length];
                ThreadLocalRandom.current().nextBytes(bytes);
                return bytes;
            })
            .withNodeBits(nodeBits)
            .build();
    }
}

Và để chứng minh rằng chúng ta không bị xung đột ngay cả khi sử dụng nhiều luồng trên cùng một nút ứng dụng, chúng ta có thể sử dụng trường hợp thử nghiệm sau

int threadCount = 16;
int iterationCount = 100_000;

CountDownLatch endLatch = new CountDownLatch(threadCount);

ConcurrentMap tsidMap = new ConcurrentHashMap<>();

long startNanos = System.nanoTime();

for (int i = 0; i < threadCount; i++) {
    final int threadId = i;
    new Thread(() -> {
        for (int j = 0; j < iterationCount; j++) {
            Tsid tsid = TsidUtil.TSID_FACTORY.create();
            assertNull(
                "TSID collision detected",
                tsidMap.put(
                    tsid, 
                    (threadId * iterationCount) + j
                )
            );
        }

        endLatch.countDown();
    }).start();
}

LOGGER.info("Starting threads");
endLatch.await();

LOGGER.info(
    "{} threads generated {} TSIDs in {} ms",
    threadCount,
    new DecimalFormat("###,###,###").format(
        threadCount * iterationCount
    ),
    TimeUnit.NANOSECONDS.toMillis(
        System.nanoTime() - startNanos
    )
);

Khi chạy thử nghiệm này, chúng tôi nhận được kết quả như sau

export TSIDCREATOR_NODE="12"
0

Không chỉ việc tạo TSID không bị xung đột mà chúng tôi còn tạo được 1. 6 triệu id trong vòng chưa đầy 800 mili giây

Sử dụng TSID làm giá trị Khóa chính

Vì TSID là một số 64-bit được sắp xếp theo thời gian nên cách tốt nhất để lưu trữ nó trong cơ sở dữ liệu là sử dụng loại cột


    com.github.f4b6a3
    tsid-creator
    ${tsid-creator.version}

7

export TSIDCREATOR_NODE="12"
1

Và, về phía ứng dụng, bạn cần sử dụng một số 64 bit, chẳng hạn như loại đối tượng Java


    com.github.f4b6a3
    tsid-creator
    ${tsid-creator.version}

8

export TSIDCREATOR_NODE="12"
2

Đó là nó

Nếu bạn thích bài viết này, tôi cá là bạn cũng sẽ thích các Khóa học Sách và Video của tôi

MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?

Và có nhiều hơn nữa

Bạn có thể kiếm được một nguồn thu nhập thụ động đáng kể từ việc quảng cáo tất cả những sản phẩm tuyệt vời này mà tôi đã và đang tạo ra

Nếu bạn quan tâm đến việc bổ sung thu nhập của mình, thì hãy tham gia chương trình liên kết của tôi

Phần kết luận

Sử dụng UUID tiêu chuẩn làm giá trị Khóa chính không phải là ý tưởng hay trừ khi các byte đầu tiên tăng đơn điệu

Vì lý do này, sử dụng TSID được sắp xếp theo thời gian là một ý tưởng tốt hơn nhiều. Nó không chỉ yêu cầu một nửa số byte như một UUID tiêu chuẩn, mà nó còn phù hợp hơn với vai trò là khóa chỉ mục B+Tree

Mặc dù SQL Server cung cấp GUID được sắp xếp theo thời gian thông qua


    com.github.f4b6a3
    tsid-creator
    ${tsid-creator.version}

9, nhưng kích thước của GUID là 128 bit, do đó, nó lớn gấp đôi TSID

Vấn đề tương tự xảy ra với phiên bản 7 của đặc tả UUID, cung cấp UUID được sắp xếp theo thời gian. Tuy nhiên, nó sử dụng cùng một định dạng chuẩn (128 bit), quá lớn. Tác động của việc lưu trữ cột Khóa chính được khuếch đại bởi mọi cột Khóa ngoại tham chiếu

Nếu tất cả các khóa Chính của bạn là UUID 128 bit, thì các chỉ mục Khóa chính và Khóa ngoại sẽ cần nhiều dung lượng, cả trên đĩa và trong bộ nhớ cơ sở dữ liệu, vì Vùng đệm chứa cả trang bảng và trang chỉ mục

Theo dõi @vlad_mihalcea

MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?

Chèn chi tiết về cách thông tin sẽ được xử lý

TẢI NGAY

MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?

MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?

MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?
MySQL có thể sử dụng UUID làm KHÓA CHÍNH không?

Có liên quan

Loại. Cơ sở dữ liệu      Thẻ. BTree, chỉ mục nhóm, Khóa chính, TSID, UUID

← Bản tin Java Persistence hiệu suất cao, Số 45

Trình nghe thực thể ngủ đông dữ liệu mùa xuân →

22 Nhận xét về “ Loại UUID tốt nhất cho Khóa chính cơ sở dữ liệu ”

  1. Armel Bobda

    Xin chân thành cảm ơn. tôi có một câu hỏi. Bạn nghĩ sao về cuid2 (https. //github. com/song song/cuid2). Đây là liên kết để triển khai Java (https. //github. com/thibaultmeyer/cuid-java)

    Trân trọng

    • vladmihalcea

      Như đã nêu trong README

      Cuid2 không tốt cho

      Id tuần tự (xem ghi chú về id có thể sắp xếp K, bên dưới)

      Đó chính là lý do TSID rất hữu ích

      • Armel Bobda

        Cảm ơn về câu trả lời của bạn

      • vladmihalcea

        Không có gì

  2. Nirmalya Sengupta

    Tôi chưa bao giờ biết về TSID. Rất cám ơn, vì đã giáo dục những người như tôi

    • vladmihalcea

      Đăng kí thêm

  3. máy mưa

    Vấn đề với tsid là chúng yêu cầu quá nhiều độ chính xác để sử dụng trong API REST với ứng dụng khách javascript
    E. g.

    Tsid tsid = TsidCreator.getTsid256();
    
    0 không thể được trình bày chính xác trong ứng dụng javascript
    “`
    để foo = 395228849641804517;
    bảng điều khiển. log("foo", foo);
    =>
    Tsid tsid = TsidCreator.getTsid256();
    
    1

    • vladmihalcea

      TSID có cả biểu diễn số và chuỗi của số đó

      Dạng số phù hợp với giá trị cột DB trong khi bạn có thể sử dụng giá trị được mã hóa Base32 trên web. Bạn có thể giải mã giá trị Chuỗi trong ứng dụng

      • kẻ trộm Rainer

        Cách duy nhất tôi có thể nghĩ để đạt được điều này trong khởi động mùa xuân/phần còn lại của dữ liệu mùa xuân/ứng dụng JPA là sử dụng

        export TSIDCREATOR_NODE="12"
        
        8 làm PK (chứ không phải là biểu diễn dài) và sau đó bạn cần thêm một
        Tsid tsid = TsidCreator.getTsid256();
        
        3, một mùa xuân
        Tsid tsid = TsidCreator.getTsid256();
        
        4,
        Tsid tsid = TsidCreator.getTsid256();
        
        5 và
        Có một lựa chọn khác mà tôi đang thiếu?

      • vladmihalcea

        Nó đơn giản hơn thế nhiều. Cái hay của Hibernate là nó có khả năng tùy biến cao, vì vậy các thực thể của bạn sẽ chỉ cần điều đó

        export TSIDCREATOR_NODE="12"
        
        3

        Và một IdentifierGenerator sẽ giải quyết vấn đề đó như được minh họa bằng ví dụ này

        https. //github. com/vladmihalcea/high-performance-java-persistence/blob/57996c68640ac3cb5ae86796533bc9f555261bf8/core/src/test/java/com/vladmihalcea/book/hpjp/hibernate/identifier/tsid/TsidIdentifierTest. java

        Bạn sẽ không cần triển khai nó vì dù sao tôi cũng sẽ tạo nó trong dự án Kiểu ngủ đông

        Đối với lớp web, dù sao cũng không nên sử dụng các thực thể ở đó, vì vậy bạn cần một bộ điều hợp để chuyển đổi/từ UI DTO

  4. Aldrin Seychell

    Bài báo tuyệt vời, cảm ơn. Bạn có bất kỳ đề xuất nào về cách lấy id nút một cách đơn giản trên Kubernetes không?

    • vladmihalcea

      Bạn có thể cung cấp mã định danh nút thông qua thuộc tính hệ thống hoặc biến môi trường đó

      Do đó, bạn có thể đặt nó cho từng nhóm

      https. // kubernetes. io/docs/tasks/inject-data-application/define-environment-variable-container/

      • Aldrin Seychell

        Tôi đã đề cập đến các cách tiếp cận khác nhau mà người ta có thể thực hiện để quyết định giá trị thực của ID nút với nỗ lực/sự phối hợp tối thiểu giữa các dịch vụ

      • vladmihalcea

        Trong trường hợp đó, cách tiếp cận đơn giản nhất là không sử dụng mã định danh nút và phân bổ tất cả 22 bit đó cho một số ngẫu nhiên

  5. Ezequiel

    Xin chào Vlad, bạn khuyên bạn nên duy trì biểu diễn nào trên cơ sở dữ liệu, định dạng chuỗi hoặc định dạng dài?

    • vladmihalcea

      Biểu diễn dài sẽ tốt hơn khi lưu nó vào DB. Chuỗi có thể được sử dụng làm định danh bên ngoài

  6. Adam

    Thú vị. Tôi biết bạn đang nói về SQL ở đây, nhưng tôi tin rằng UUID gần như là tiêu chuẩn cho các định danh bản ghi trong NoSQL (dường như chắc chắn là ở Mongo, đây là cái duy nhất tôi đã sử dụng nhiều). Khóa ngoại không phải là vấn đề quá lớn ở đó, nhưng việc lập chỉ mục vẫn là vấn đề. Làm thế nào mà họ giải quyết được các vấn đề về lập chỉ mục – hay chúng ta cũng nên sử dụng thứ gì đó khác ở đó?

    • vladmihalcea

      Đối với kho lưu trữ LSM, việc UUID không được sắp xếp theo thời gian không phải là vấn đề, nhưng dung lượng thừa là

      Nhiều ứng dụng có thể hoạt động tốt với Tsid 64 bit. Thực tế là nhiều người sử dụng UUID là họ không biết có những lựa chọn thay thế tốt hơn

  7. Norbi

    ULID có cách tiếp cận rất giống nhau. https. //github. com/ulid/spec

    • vladmihalcea

      Vâng đó là sự thật. Nhưng, ULID giống như phiên bản 7 của UUID. Mặc dù được sắp xếp theo thời gian nhưng nó vẫn rất lớn. Tsid là một nửa kích thước

      Bạn có thể sử dụng UUID làm khóa chính không?

      Sử dụng UUID tiêu chuẩn làm giá trị Khóa chính không phải là ý kiến ​​hay trừ khi các byte đầu tiên tăng đều đặn . Vì lý do này, sử dụng TSID được sắp xếp theo thời gian là một ý tưởng tốt hơn nhiều. Nó không chỉ yêu cầu một nửa số byte như một UUID tiêu chuẩn, mà nó còn phù hợp hơn với vai trò là khóa chỉ mục B+Tree.

      Chúng ta có thể sử dụng UUID trong MySQL không?

      Hàm UUID() trong MySQL . Nó được thiết kế như một con số duy nhất trên toàn cầu. This function in MySQL is used to return a Universal Unique Identifier (UUID) generated according to RFC 4122, “A Universally Unique Identifier (UUID) URN Namespace”. It is designed as a number that is universally unique.

      Tôi có nên sử dụng UUID thay vì ID không?

      Bằng cách sử dụng UUID, bạn đảm bảo rằng ID của mình không chỉ là duy nhất trong ngữ cảnh của một bảng cơ sở dữ liệu hoặc ứng dụng web mà còn thực sự là duy nhất trong toàn bộ . Không có ID nào khác tồn tại phải giống với ID của bạn.