[{"data":1,"prerenderedAt":623},["ShallowReactive",2],{"guide-uuids-as-database-keys":3},{"id":4,"title":5,"body":6,"date":615,"description":616,"extension":617,"meta":618,"navigation":77,"path":619,"readingTime":74,"seo":620,"stem":621,"__hash__":622},"guides\u002Fguides\u002Fuuids-as-database-keys.md","When to Use UUIDs as Database Primary Keys",{"type":7,"value":8,"toc":595},"minimark",[9,14,18,22,27,30,97,101,134,138,141,144,189,193,253,257,261,264,289,293,360,370,374,377,405,409,424,427,507,511,514,541,544,548,573,577,586,591],[10,11,13],"h2",{"id":12},"the-primary-key-decision","The Primary Key Decision",[15,16,17],"p",{},"Choosing between auto-incrementing integers and UUIDs as primary keys affects your entire system. The right choice depends on your scale, architecture, and operational requirements. Neither option is universally better — each carries distinct tradeoffs.",[10,19,21],{"id":20},"auto-increment-ids-simple-and-fast","Auto-Increment IDs: Simple and Fast",[23,24,26],"h3",{"id":25},"advantages","Advantages",[15,28,29],{},"Sequential integer keys provide the best insert performance because database engines optimize for sequential writes. B-tree indexes stay balanced, pages fill efficiently, and the database never needs to split pages for out-of-order inserts.",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-sql shiki shiki-themes github-light github-dark","-- PostgreSQL auto-increment\nCREATE TABLE users (\n  id SERIAL PRIMARY KEY,\n  email TEXT NOT NULL UNIQUE\n);\n\n-- Insert is fast — appends to end of index\nINSERT INTO users (email) VALUES ('user@example.com');\n-- id = 1, then 2, then 3...\n","sql","",[38,39,40,48,54,60,66,72,79,85,91],"code",{"__ignoreMap":36},[41,42,45],"span",{"class":43,"line":44},"line",1,[41,46,47],{},"-- PostgreSQL auto-increment\n",[41,49,51],{"class":43,"line":50},2,[41,52,53],{},"CREATE TABLE users (\n",[41,55,57],{"class":43,"line":56},3,[41,58,59],{},"  id SERIAL PRIMARY KEY,\n",[41,61,63],{"class":43,"line":62},4,[41,64,65],{},"  email TEXT NOT NULL UNIQUE\n",[41,67,69],{"class":43,"line":68},5,[41,70,71],{},");\n",[41,73,75],{"class":43,"line":74},6,[41,76,78],{"emptyLinePlaceholder":77},true,"\n",[41,80,82],{"class":43,"line":81},7,[41,83,84],{},"-- Insert is fast — appends to end of index\n",[41,86,88],{"class":43,"line":87},8,[41,89,90],{},"INSERT INTO users (email) VALUES ('user@example.com');\n",[41,92,94],{"class":43,"line":93},9,[41,95,96],{},"-- id = 1, then 2, then 3...\n",[23,98,100],{"id":99},"limitations","Limitations",[102,103,104,116,122,128],"ul",{},[105,106,107,111,112,115],"li",{},[108,109,110],"strong",{},"Not unique across tables or databases",": ID 42 in ",[38,113,114],{},"users"," means nothing without the table context",[105,117,118,121],{},[108,119,120],{},"Predictable",": Attackers can enumerate records by incrementing IDs in API requests",[105,123,124,127],{},[108,125,126],{},"Collision in distributed systems",": Two servers generating IDs independently will produce duplicate values",[105,129,130,133],{},[108,131,132],{},"Merge conflicts",": Combining data from two databases with auto-increment keys requires remapping",[10,135,137],{"id":136},"uuids-globally-unique-without-coordination","UUIDs: Globally Unique Without Coordination",[23,139,26],{"id":140},"advantages-1",[15,142,143],{},"UUIDs provide uniqueness without any central coordinator. Any system can generate a UUID at any time, and the probability of collision is negligible — approximately 1 in 2^122 for UUIDv4.",[31,145,147],{"className":33,"code":146,"language":35,"meta":36,"style":36},"-- PostgreSQL UUID primary key\nCREATE TABLE users (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  email TEXT NOT NULL UNIQUE\n);\n\n-- Insert — UUID generated without coordination\nINSERT INTO users (email) VALUES ('user@example.com');\n-- id = a]b3f2c8-7d1a-4e9f-b5c6-1234567890ab\n",[38,148,149,154,158,163,167,171,175,180,184],{"__ignoreMap":36},[41,150,151],{"class":43,"line":44},[41,152,153],{},"-- PostgreSQL UUID primary key\n",[41,155,156],{"class":43,"line":50},[41,157,53],{},[41,159,160],{"class":43,"line":56},[41,161,162],{},"  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n",[41,164,165],{"class":43,"line":62},[41,166,65],{},[41,168,169],{"class":43,"line":68},[41,170,71],{},[41,172,173],{"class":43,"line":74},[41,174,78],{"emptyLinePlaceholder":77},[41,176,177],{"class":43,"line":81},[41,178,179],{},"-- Insert — UUID generated without coordination\n",[41,181,182],{"class":43,"line":87},[41,183,90],{},[41,185,186],{"class":43,"line":93},[41,187,188],{},"-- id = a]b3f2c8-7d1a-4e9f-b5c6-1234567890ab\n",[23,190,192],{"id":191},"when-uuids-win","When UUIDs Win",[194,195,196,209],"table",{},[197,198,199],"thead",{},[200,201,202,206],"tr",{},[203,204,205],"th",{},"Scenario",[203,207,208],{},"Why UUIDs Help",[210,211,212,221,229,237,245],"tbody",{},[200,213,214,218],{},[215,216,217],"td",{},"Multi-master replication",[215,219,220],{},"Each node generates IDs without conflicts",[200,222,223,226],{},[215,224,225],{},"Microservices architecture",[215,227,228],{},"Services create entities independently",[200,230,231,234],{},[215,232,233],{},"Offline-first applications",[215,235,236],{},"Clients generate IDs without connectivity",[200,238,239,242],{},[215,240,241],{},"Merging datasets",[215,243,244],{},"No key remapping needed",[200,246,247,250],{},[215,248,249],{},"Public API identifiers",[215,251,252],{},"Prevents enumeration attacks",[10,254,256],{"id":255},"performance-considerations","Performance Considerations",[23,258,260],{"id":259},"insert-performance","Insert Performance",[15,262,263],{},"Random UUIDs (v4) cause B-tree page splits because new values can land anywhere in the index. PostgreSQL resolves this partially with its UUID primary key optimization, but sequential keys still outperform for heavy write workloads.",[31,265,267],{"className":33,"code":266,"language":35,"meta":36,"style":36},"-- Benchmark approximation (10M rows)\n-- SERIAL:          ~45 seconds\n-- UUID v4:         ~75 seconds\n-- UUID v7:         ~50 seconds (time-ordered)\n",[38,268,269,274,279,284],{"__ignoreMap":36},[41,270,271],{"class":43,"line":44},[41,272,273],{},"-- Benchmark approximation (10M rows)\n",[41,275,276],{"class":43,"line":50},[41,277,278],{},"-- SERIAL:          ~45 seconds\n",[41,280,281],{"class":43,"line":56},[41,282,283],{},"-- UUID v4:         ~75 seconds\n",[41,285,286],{"class":43,"line":62},[41,287,288],{},"-- UUID v7:         ~50 seconds (time-ordered)\n",[23,290,292],{"id":291},"storage-size","Storage Size",[194,294,295,308],{},[197,296,297],{},[200,298,299,302,305],{},[203,300,301],{},"Key Type",[203,303,304],{},"Size (bytes)",[203,306,307],{},"Index Size Impact",[210,309,310,324,338,349],{},[200,311,312,318,321],{},[215,313,314,317],{},[38,315,316],{},"SERIAL"," (INT32)",[215,319,320],{},"4",[215,322,323],{},"Minimal",[200,325,326,332,335],{},[215,327,328,331],{},[38,329,330],{},"BIGSERIAL"," (INT64)",[215,333,334],{},"8",[215,336,337],{},"Low",[200,339,340,343,346],{},[215,341,342],{},"UUID",[215,344,345],{},"16",[215,347,348],{},"2-4x larger indexes",[200,350,351,354,357],{},[215,352,353],{},"UUID as TEXT",[215,355,356],{},"36",[215,358,359],{},"4-9x larger indexes",[15,361,362,363,365,366,369],{},"Always store UUIDs as the native ",[38,364,342],{}," type, not as ",[38,367,368],{},"TEXT",". The binary format is 16 bytes versus 36 bytes for the string representation. This difference compounds across every foreign key reference and index.",[23,371,373],{"id":372},"time-ordered-uuids","Time-Ordered UUIDs",[15,375,376],{},"UUIDv7 solves the page-split problem by encoding a timestamp in the first 48 bits. New values arrive in roughly sequential order, preserving B-tree insertion efficiency:",[31,378,382],{"className":379,"code":380,"language":381,"meta":36,"style":36},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F UUIDv7 structure (simplified)\n\u002F\u002F First 48 bits: Unix timestamp in milliseconds\n\u002F\u002F Remaining 74 bits: random data\n\u002F\u002F Result: roughly sequential, globally unique\n","javascript",[38,383,384,390,395,400],{"__ignoreMap":36},[41,385,386],{"class":43,"line":44},[41,387,389],{"class":388},"sJ8bj","\u002F\u002F UUIDv7 structure (simplified)\n",[41,391,392],{"class":43,"line":50},[41,393,394],{"class":388},"\u002F\u002F First 48 bits: Unix timestamp in milliseconds\n",[41,396,397],{"class":43,"line":56},[41,398,399],{"class":388},"\u002F\u002F Remaining 74 bits: random data\n",[41,401,402],{"class":43,"line":62},[41,403,404],{"class":388},"\u002F\u002F Result: roughly sequential, globally unique\n",[10,406,408],{"id":407},"foreign-key-impact","Foreign Key Impact",[15,410,411,412,415,416,419,420,423],{},"Every foreign key column inherits the primary key type. If your primary key is a UUID, every ",[38,413,414],{},"user_id",", ",[38,417,418],{},"order_id",", and ",[38,421,422],{},"comment_id"," column across your entire schema stores 16 bytes instead of 4.",[15,425,426],{},"For a table with 100 million rows and 5 UUID foreign keys, that is 6 GB of additional storage compared to integer keys. Indexes on those foreign keys grow proportionally.",[31,428,430],{"className":33,"code":429,"language":35,"meta":36,"style":36},"-- With UUID keys\nCREATE TABLE orders (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  user_id UUID NOT NULL REFERENCES users(id),\n  product_id UUID NOT NULL REFERENCES products(id)\n  -- Two UUID FKs = 32 bytes per row just for references\n);\n\n-- With integer keys\nCREATE TABLE orders (\n  id SERIAL PRIMARY KEY,\n  user_id INT NOT NULL REFERENCES users(id),\n  product_id INT NOT NULL REFERENCES products(id)\n  -- Two INT FKs = 8 bytes per row for references\n);\n",[38,431,432,437,442,446,451,456,461,465,469,474,479,484,490,496,502],{"__ignoreMap":36},[41,433,434],{"class":43,"line":44},[41,435,436],{},"-- With UUID keys\n",[41,438,439],{"class":43,"line":50},[41,440,441],{},"CREATE TABLE orders (\n",[41,443,444],{"class":43,"line":56},[41,445,162],{},[41,447,448],{"class":43,"line":62},[41,449,450],{},"  user_id UUID NOT NULL REFERENCES users(id),\n",[41,452,453],{"class":43,"line":68},[41,454,455],{},"  product_id UUID NOT NULL REFERENCES products(id)\n",[41,457,458],{"class":43,"line":74},[41,459,460],{},"  -- Two UUID FKs = 32 bytes per row just for references\n",[41,462,463],{"class":43,"line":81},[41,464,71],{},[41,466,467],{"class":43,"line":87},[41,468,78],{"emptyLinePlaceholder":77},[41,470,471],{"class":43,"line":93},[41,472,473],{},"-- With integer keys\n",[41,475,477],{"class":43,"line":476},10,[41,478,441],{},[41,480,482],{"class":43,"line":481},11,[41,483,59],{},[41,485,487],{"class":43,"line":486},12,[41,488,489],{},"  user_id INT NOT NULL REFERENCES users(id),\n",[41,491,493],{"class":43,"line":492},13,[41,494,495],{},"  product_id INT NOT NULL REFERENCES products(id)\n",[41,497,499],{"class":43,"line":498},14,[41,500,501],{},"  -- Two INT FKs = 8 bytes per row for references\n",[41,503,505],{"class":43,"line":504},15,[41,506,71],{},[10,508,510],{"id":509},"hybrid-approach","Hybrid Approach",[15,512,513],{},"Many production systems use both: a sequential integer as the primary key for internal joins and indexing, and a UUID exposed externally as a public identifier.",[31,515,517],{"className":33,"code":516,"language":35,"meta":36,"style":36},"CREATE TABLE users (\n  id SERIAL PRIMARY KEY,          -- internal, fast joins\n  public_id UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),  -- external, safe to expose\n  email TEXT NOT NULL UNIQUE\n);\n",[38,518,519,523,528,533,537],{"__ignoreMap":36},[41,520,521],{"class":43,"line":44},[41,522,53],{},[41,524,525],{"class":43,"line":50},[41,526,527],{},"  id SERIAL PRIMARY KEY,          -- internal, fast joins\n",[41,529,530],{"class":43,"line":56},[41,531,532],{},"  public_id UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),  -- external, safe to expose\n",[41,534,535],{"class":43,"line":62},[41,536,65],{},[41,538,539],{"class":43,"line":68},[41,540,71],{},[15,542,543],{},"This gives you the performance of integer keys internally while protecting against enumeration and simplifying distributed generation externally.",[10,545,547],{"id":546},"key-takeaways","Key Takeaways",[102,549,550,553,556,559,567,570],{},[105,551,552],{},"Auto-increment integers provide the best insert performance and smallest storage footprint",[105,554,555],{},"UUIDs eliminate coordination overhead in distributed and multi-master systems",[105,557,558],{},"Random UUIDs (v4) cause B-tree page splits; time-ordered UUIDs (v7) mitigate this",[105,560,561,562,564,565],{},"Always store UUIDs as native ",[38,563,342],{}," type, never as ",[38,566,368],{},[105,568,569],{},"Foreign key columns multiply the storage difference — consider the full schema impact",[105,571,572],{},"A hybrid approach separates internal performance from external safety",[10,574,576],{"id":575},"try-it-yourself","Try It Yourself",[15,578,579,580,585],{},"Generate UUIDs in any version with our free ",[581,582,584],"a",{"href":583},"\u002Ftools\u002Fuuid-generator","UUID Generator",". Create UUIDv4, UUIDv7, and other versions instantly — all processing happens locally in your browser.",[15,587,588],{},[581,589,590],{"href":583},"Try the UUID Generator →",[592,593,594],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":36,"searchDepth":50,"depth":50,"links":596},[597,598,602,606,611,612,613,614],{"id":12,"depth":50,"text":13},{"id":20,"depth":50,"text":21,"children":599},[600,601],{"id":25,"depth":56,"text":26},{"id":99,"depth":56,"text":100},{"id":136,"depth":50,"text":137,"children":603},[604,605],{"id":140,"depth":56,"text":26},{"id":191,"depth":56,"text":192},{"id":255,"depth":50,"text":256,"children":607},[608,609,610],{"id":259,"depth":56,"text":260},{"id":291,"depth":56,"text":292},{"id":372,"depth":56,"text":373},{"id":407,"depth":50,"text":408},{"id":509,"depth":50,"text":510},{"id":546,"depth":50,"text":547},{"id":575,"depth":50,"text":576},"2026-05-28","Evaluate using UUIDs as database primary keys. Compare UUID vs auto-increment ID tradeoffs for distributed systems, replication, and performance.","md",{"immutable":77},"\u002Fguides\u002Fuuids-as-database-keys",{"title":5,"description":616},"guides\u002Fuuids-as-database-keys","8c88XrBhJWs3uLOLIOveuUs48W8sYIkFOsv8Q9JzOIU",1780401338205]