Architecture & Databases 12 min read

How to Store UUIDs in a Database: String vs Binary vs Native UUID

By FastUUID Engineering Team •

Imagine you are building an online store, a social media app, or a mobile game. Every time a new user signs up or places an order, your database needs to assign that record a unique identity badge so it never gets confused with anything else.

Many systems start with simple numbers: User #1, User #2, User #3. But as applications grow across multiple servers, simple numbers create serious security and scalability headaches. Competitors can guess your total sales volume (/order/100 vs /order/101), and two different database servers might accidentally create the same ID at the exact same time.

To solve this, developers use UUIDs (Universally Unique Identifiers)—huge, 128-bit numbers that are mathematically guaranteed to be globally unique.

Once you decide to use UUIDs, you face an immediate technical dilemma:

“How should I actually store this UUID in my database?”

Should you store it as a normal readable text string like CHAR(36)? As raw computer bytes like BINARY(16)? Or does your database have a special “Native UUID” button built right in?

In this beginner-friendly guide, we will break down how UUID storage works, compare String vs. Binary vs. Native UUID, explain the hidden impact on database performance and indexes, and give you clear recommendations for PostgreSQL, MySQL, SQL Server, and SQLite.


1. Quick Summary: Which Format Should You Choose?

If you just want the fast answer for your database, here is the golden rule:

┌────────────────────────────────────────────────────────────────────────┐
│                   THE GOLDEN RULE OF UUID DATABASE STORAGE             │
├───────────────────┬───────────────────────────────┬────────────────────┤
│ Database          │ Recommended Storage Type      │ Physical Storage   │
├───────────────────┼───────────────────────────────┼────────────────────┤
│ PostgreSQL        │ Native `UUID` type            │ 16 bytes (Fast!)   │
│ MySQL 8.0+        │ `BINARY(16)` with functions   │ 16 bytes (Fast!)   │
│ SQL Server (.NET) │ Native `UNIQUEIDENTIFIER`     │ 16 bytes (Fast!)   │
│ SQLite            │ `TEXT` (or `BLOB`)            │ 36 or 16 bytes     │
│ MongoDB           │ Native `BinData(4, ...)`      │ 16 bytes (Fast!)   │
│ Small / Prototype │ `VARCHAR(36)` or `CHAR(36)`   │ 36 bytes (Readable)│
└───────────────────┴───────────────────────────────┴────────────────────┘
  • Best Rule of Thumb: If your database offers a native UUID type (like PostgreSQL and SQL Server), always use it. You get the ultra-compact 16-byte storage speed of binary data combined with the effortless human readability of text.
  • If you use MySQL: Use BINARY(16) for high-volume production tables, or CHAR(36) if you have a small project and prioritize readability.

If you need to generate UUIDs to test your database schema right now, generate them instantly with our free UUID Generator.


2. What Is a UUID Under the Hood? (Text vs. Binary)

To understand database storage, think of a UUID like an address on an envelope.

A UUID is fundamentally a 128-bit number (16 bytes). But humans do not read raw binary bits well. So, software formats those 16 bytes into 32 hexadecimal characters separated by 4 hyphens:

550e8400 - e29b - 41d4 - a716 - 446655440000

The “Spelling Out Numbers” Analogy:

Think about the number 1,000,000 (one million):

  • You can write it as the digit 1000000 (compact and fast for a computer calculator to process).
  • Or you can spell out every English letter: "O-N-E M-I-L-L-I-O-N" (easy for a child to read, but takes up 13 times more space on the page).

This is the exact difference between Binary and String storage:

┌────────────────────────────────────────────────────────────────────────┐
│                      STRING VS BINARY STORAGE SIZE                     │
├────────────────────────────────────────────────────────────────────────┤
│ 1. Text String Format:  "550e8400-e29b-41d4-a716-446655440000"        │
│    • Length: 36 ASCII characters                                       │
│    • Storage: 36 bytes of disk space per row                           │
│                                                                        │
│ 2. Raw Binary Format:   0x550E8400E29B41D4A716446655440000             │
│    • Length: 16 raw binary bytes                                       │
│    • Storage: 16 bytes of disk space per row                           │
│                                                                        │
│ Savings: Storing raw binary uses 55% LESS disk space than text!        │
└────────────────────────────────────────────────────────────────────────┘

Every time you store a UUID as text, you are storing 36 separate letters and punctuation marks instead of the 16 raw numbers the computer actually needs.


3. The Three Ways to Store UUIDs

Let us examine the three main storage approaches developers use:

┌────────────────────────────────────────────────────────────────────────┐
│                        THE THREE UUID STORAGE STYLES                   │
├───────────────────────────┬───────────────────────────┬────────────────┤
│ 1. String / Text          │ 2. Raw Binary             │ 3. Native UUID │
│ (CHAR / VARCHAR)          │ (BINARY / BLOB / BYTEA)   │ (PostgreSQL/MS)│
├───────────────────────────┼───────────────────────────┼────────────────┤
│ • 36 bytes per row        │ • 16 bytes per row        │ • 16 bytes raw │
│ • Human-readable text     │ • Unreadable hex blob     │ • Auto-text    │
│ • Simple SQL queries      │ • Needs conversion funcs  │ • Best of both │
└───────────────────────────┴───────────────────────────┴────────────────┘

Option 1: String Storage (CHAR(36) or VARCHAR(36))

In this approach, you tell the database to store the UUID as plain text:

CREATE TABLE users (
    id CHAR(36) PRIMARY KEY,
    email VARCHAR(255) NOT NULL
);
  • The Good: It is 100% human-readable. When you run SELECT * FROM users;, you immediately see 550e8400-e29b-41d4.... It is effortless to debug in database GUIs like DBeaver, pgAdmin, or MySQL Workbench.
  • The Bad: It consumes 36 bytes per row (or 32 bytes if you strip hyphens). That is more than double the necessary space.

Beginner Tip: Why CHAR(36) instead of VARCHAR(36)?
If you store UUIDs as text, always choose CHAR(36) over VARCHAR(36). VARCHAR is designed for text of variable lengths (like names or emails) and stores extra metadata bytes to track how long each string is. Because canonical UUIDs are always exactly 36 characters long, CHAR(36) avoids that extra overhead.

Option 2: Binary Storage (BINARY(16))

In databases without a native UUID type (like older MySQL), developers store the raw 16 bytes directly:

CREATE TABLE users (
    id BINARY(16) PRIMARY KEY,
    email VARCHAR(255) NOT NULL
);
  • The Good: Super compact! Exactly 16 bytes per row. Your tables and indexes are small, fast, and cache-friendly.
  • The Bad: It is completely unreadable to humans. If you run a query, the ID looks like scrambled gibberish (0x550E84...). You have to remember to wrap your IDs in conversion functions like HEX() or BIN_TO_UUID() in every SQL query.

Option 3: Native UUID Type (The Dream Solution)

Modern relational databases like PostgreSQL and Microsoft SQL Server offer a built-in UUID type:

-- PostgreSQL
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) NOT NULL
);
  • The Superpower: The database engine stores the data internally as ultra-compact 16 binary bytes, but whenever you insert or query data, it accepts and displays it as friendly 36-character text. You get maximum performance without sacrificing human readability!

4. Why Storage Size Matters: The Hidden Index Penalty

You might wonder: “My hard drive has 2 Terabytes of space. Why should I care if a UUID takes 16 bytes or 36 bytes?”

Disk space is cheap, but RAM (Computer Memory) is extremely expensive.

To make queries fast, databases build B-Tree Indexes (like the index in the back of a textbook). Every index on your table keeps a full copy of the Primary Key column.

The Notebook Margins Analogy:

Imagine you are taking notes in a spiral notebook:

  • If you write large, thick text that takes up 4 lines per sentence, your notebook fills up in 20 pages. You have to constantly stop, flip pages, and fetch new notebooks from your backpack.
  • If you write neatly on single lines, the same notebook holds 200 pages. Everything you need stays open right on your desk.

In a database, your desk is RAM (specifically the Database Buffer Pool). The database wants to keep all index pages loaded in fast memory.

┌────────────────────────────────────────────────────────────────────────┐
│               STORAGE IMPACT ON 10,000,000 DATABASE ROWS               │
├───────────────────────────┬───────────────────┬────────────────────────┤
│ Storage Format            │ Primary Key Size  │ 3 Secondary Indexes    │
├───────────────────────────┼───────────────────┼────────────────────────┤
│ Native / BINARY(16)       │ ~160 MB           │ ~480 MB                │
│ CHAR(36) String           │ ~360 MB           │ ~1,080 MB (Over 1 GB!) │
├───────────────────────────┼───────────────────┼────────────────────────┤
│ Extra RAM Wasted:         │ +200 MB           │ +600 MB Extra in Cache │
└───────────────────────────┴───────────────────┴────────────────────────┘

When your indexes balloon from 480 MB to over 1 GB, your server can no longer fit the index in RAM. It has to constantly read from the slow physical SSD, creating lag and slowing down your API endpoints.


5. Random UUIDs (v4) vs. Time-Ordered UUIDs (v7)

Storage type is only half the battle. The version of UUID you choose can make or break your database speed.

The Library Bookshelf Analogy:

Imagine you are a librarian adding books to a library shelf arranged strictly in alphabetical order by title:

  • UUID v4 (Random): Each new book title starts with a completely random letter. Book 1 starts with “Z”, Book 2 starts with “A”, Book 3 starts with “M”. To insert “M”, you have to physically shove all the books to the right, split the shelf in half, and buy a new shelf. In databases, this is called B-Tree Page Splitting and Index Fragmentation.
  • UUID v7 (Time-Ordered): Each book is stamped with the exact date and second it arrived. Every single new book is placed neatly at the very end of the shelf. Zero shoving, zero page splitting, blazing fast inserts!
┌────────────────────────────────────────────────────────────────────────┐
│                  HOW UUID VERSIONS AFFECT B-TREE INDEXES               │
├───────────────────────────┬────────────────────────────────────────────┤
│ UUID v4 (Pure Random)     │ Inserter jumps randomly across entire tree.│
│                           │ High page fragmentation, heavy disk I/O.   │
├───────────────────────────┼────────────────────────────────────────────┤
│ UUID v7 (Time-Ordered)    │ New rows append sequentially to the right. │
│ (RFC 9562 Recommended)    │ Compact indexes, optimal cache efficiency. │
└───────────────────────────┴────────────────────────────────────────────┘

If you are choosing UUIDs for a primary key in 2026, always prefer UUID v7 over UUID v4.

You can inspect the timestamps inside your identifiers with our UUID Decoder & Analyzer.


6. Real Database Examples: PostgreSQL, MySQL, and SQL Server

Here is how to store and query UUIDs properly across the top databases:

1. PostgreSQL (The Gold Standard)

PostgreSQL has had first-class native UUID support since version 8.3:

-- Create table with Native UUID
CREATE TABLE customers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name TEXT NOT NULL
);

-- Insert a record
INSERT INTO customers (name) VALUES ('Alice Smith');

-- Query a record (Clean text syntax, 16-byte binary speed under the hood)
SELECT * FROM customers WHERE id = '550e8400-e29b-41d4-a716-446655440000';

2. MySQL 8.0+ (BINARY(16) with Built-in Functions)

MySQL does not have a native UUID type keyword, but MySQL 8.0 introduced two built-in helper functions: UUID_TO_BIN() and BIN_TO_UUID().

-- Create table with BINARY(16)
CREATE TABLE orders (
    id BINARY(16) PRIMARY KEY,
    total_amount DECIMAL(10, 2) NOT NULL
);

-- Insert a UUID string by converting it to 16 binary bytes
-- Tip: The second argument '1' swaps time bits for better index locality!
INSERT INTO orders (id, total_amount) 
VALUES (UUID_TO_BIN('018f3b7d-3b7d-7bad-9bdd-2b0d7b3dcb6d', 1), 99.50);

-- Read the order back as readable text
SELECT BIN_TO_UUID(id, 1) AS id_text, total_amount FROM orders;

If you frequently work with MySQL GUIDs or Windows systems, you can generate valid identifiers with our free GUID Generator.

3. Microsoft SQL Server (UNIQUEIDENTIFIER)

SQL Server features a native 16-byte UNIQUEIDENTIFIER type:

-- Create table
CREATE TABLE products (
    id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    title NVARCHAR(100) NOT NULL
);

-- Query using standard string syntax
SELECT * FROM products WHERE id = '550e8400-e29b-41d4-a716-446655440000';

7. Storage Format Comparison Matrix

FeatureCHAR(36) StringCHAR(32) HexBINARY(16)Native UUID
Physical Storage Size36 bytes32 bytes16 bytes16 bytes
Index EfficiencyLowMediumMaximumMaximum
Readability in Queries✅ Instant text⚠️ Stripped text❌ Raw hex bytes✅ Instant text
Needs Conversion Code❌ No⚠️ Hyphen stripper⚠️ Yes (BIN_TO_UUID)❌ No
Ease of Debugging⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Best Used InSmall apps, SQLiteLegacy APIsHigh-volume MySQLPostgreSQL, SQL Server

If you need to validate syntax and verify that an identifier has the correct 8-4-4-4-12 hyphen format, run it through our free UUID / GUID Validator.


8. Common Mistakes When Storing UUIDs

┌────────────────────────────────────────────────────────────────────────┐
│                   COMMON UUID DATABASE STORAGE MISTAKES                │
├───────────────────┬────────────────────────────────────────────────────┤
│ 1. Using VARCHAR  │ Allocates variable length tracking bytes on a      │
│    Instead of CHAR│ string that is ALWAYS 36 characters long.          │
├───────────────────┼────────────────────────────────────────────────────┤
│ 2. Stripping      │ Removing hyphens saves 4 bytes, but breaks RFC     │
│    Hyphens        │ standards and slows down client-side JSON parsers. │
├───────────────────┼────────────────────────────────────────────────────┤
│ 3. Case Mismatches│ Storing uppercase UUIDs in MySQL and querying with │
│                   │ lowercase can cause index collation penalties.     │
├───────────────────┼────────────────────────────────────────────────────┤
│ 4. Using Random v4│ Causes severe B-tree page fragmentation at scale.  │
│    as Primary Key │ Use time-ordered UUID v7 for sequential inserts.   │
├───────────────────┼────────────────────────────────────────────────────┤
│ 5. Storing Base64 │ Storing 22-char Base64 directly in a DB column is  │
│    in Database    │ non-standard. Store Native/Binary; use Base64 only │
│                   │ for public URLs and APIs.                          │
└───────────────────┴────────────────────────────────────────────────────┘

If you want to compress UUIDs for web URLs or API slugs without cluttering your database schema, check out our Base64 UUID Generator.


9. How to Migrate from Strings to Binary in Production

If your existing database already has millions of rows stored as VARCHAR(36) and your server is slowing down, follow this zero-downtime migration strategy:

  1. Add the new column: Add id_binary BINARY(16) (or UUID in PostgreSQL) alongside your existing column.
  2. Backfill in batches: Write a background script to convert rows in batches of 5,000 using UUID_TO_BIN(id).
  3. Dual-write in application code: Update your backend API to write to both columns on new INSERT operations.
  4. Switch foreign keys and indexes: Rebuild foreign keys to reference the new 16-byte column.
  5. Drop the old text column: Once verified, drop the original 36-byte column and reclaim your disk space!

If you need to batch-generate large numbers of test identifiers to benchmark your database performance, use our free Bulk UUID Generator.


10. Frequently Asked Questions (FAQ)

Should I store a UUID as CHAR or BINARY?

If your database supports a native UUID type (like PostgreSQL), always use native UUID. In MySQL, choose BINARY(16) for high-scale performance, or CHAR(36) if your table has fewer than 100,000 rows and you prefer easy visual debugging.

Is BINARY(16) really faster than VARCHAR(36)?

Yes. BINARY(16) consumes less than half the storage space (16 bytes vs 36+ bytes). Because indexes are 55% smaller, more of your database fits in RAM cache, resulting in significantly faster query lookups and lower disk I/O.

Should a UUID be used as a primary key?

Yes, but you should use UUID v7 (time-ordered) instead of UUID v4 (purely random). UUID v7 inserts sequentially into database B-tree indexes, preventing index fragmentation and performance degradation.

Does storing UUIDs without hyphens improve database performance?

Stripping hyphens reduces string storage from 36 bytes to 32 bytes (CHAR(32)), saving about 11%. However, BINARY(16) saves 55% while maintaining strict data integrity. If performance matters, use binary instead of stripping hyphens.

Can I convert a binary UUID back to a normal UUID string?

Yes. In MySQL, call BIN_TO_UUID(binary_column). In Python, call str(uuid.UUID(bytes=binary_data)). In JavaScript, convert the 16 bytes to hexadecimal and insert hyphens.

Does PostgreSQL have a native UUID data type?

Yes. PostgreSQL includes a built-in UUID data type that stores 16 raw bytes internally while automatically parsing and displaying standard 36-character hyphenated text in queries.


11. Conclusion & Developer Tools

Choosing the right UUID database storage comes down to understanding the trade-off between human readability and binary efficiency:

  • In PostgreSQL or SQL Server: Always choose the Native UUID type (UUID / UNIQUEIDENTIFIER).
  • In high-traffic MySQL: Use BINARY(16) with UUID_TO_BIN() and prefer UUID v7 to keep B-tree indexes fast.
  • In prototypes or small hobby projects: CHAR(36) is completely fine and easy to inspect.

Explore our full suite of free developer tools on FastUUIDGenerator.com:

Need to generate UUIDs right now?

Generate cryptographically secure UUID v4 or time-ordered UUID v7 identifiers instantly in your browser.