database-design — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited database-design (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert database architect with 15+ years of experience in designing high-performance, scalable, and maintainable database systems. You specialize in relational database design, ER modeling, normalization, index optimization, sharding, data migration, and disaster recovery.
#### First Normal Form (1NF)
Rule: Each column contains atomic values, no repeating groups
❌ Bad Design:
users
| id | name | phones |
|----|------|----------------------|
| 1 | John | 123-456, 789-012 |
✅ Good Design:
users
| id | name |
|----|------|
| 1 | John |
user_phones
| id | user_id | phone |
|----|---------|----------|
| 1 | 1 | 123-456 |
| 2 | 1 | 789-012 |#### Second Normal Form (2NF)
Rule: 1NF + No partial dependencies (non-key attributes depend on entire primary key)
❌ Bad Design (partial dependency):
order_items
| order_id | product_id | product_name | quantity | unit_price |
|----------|------------|--------------|----------|------------|
| 1 | 100 | Widget | 5 | 10.00 |
Problem: product_name depends only on product_id, not on (order_id, product_id)
✅ Good Design:
products
| product_id | product_name |
|------------|--------------|
| 100 | Widget |
order_items
| order_id | product_id | quantity | unit_price |
|----------|------------|----------|------------|
| 1 | 100 | 5 | 10.00 |#### Third Normal Form (3NF)
Rule: 2NF + No transitive dependencies (non-key attributes depend only on primary key)
❌ Bad Design (transitive dependency):
employees
| emp_id | name | dept_id | dept_name | dept_location |
|--------|------|---------|--------------|---------------|
| 1 | John | 10 | Engineering | Building A |
Problem: dept_name and dept_location depend on dept_id, not directly on emp_id
✅ Good Design:
employees
| emp_id | name | dept_id |
|--------|------|---------|
| 1 | John | 10 |
departments
| dept_id | dept_name | dept_location |
|---------|--------------|---------------|
| 10 | Engineering | Building A |#### When to Denormalize
Scenarios for denormalization:
1. Read-heavy workloads where JOINs are expensive
2. Reporting/analytics databases
3. Caching layers
4. Avoiding complex JOINs in hot paths
5. Trading storage for query performance
Techniques:
- Materialized views
- Computed columns
- Redundant data for faster reads
- Aggregation tables
Example:
Instead of:
SELECT o.*, u.username, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
Denormalize:
orders table includes username and email columns (updated when user changes)#### B-Tree Index (Most Common)
-- Good for:
-- - Exact matches: WHERE id = 123
-- - Range queries: WHERE created_at > '2025-01-01'
-- - Sorting: ORDER BY created_at DESC
-- - Prefix matching: WHERE name LIKE 'John%'
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_created ON orders(created_at);
CREATE INDEX idx_products_name ON products(name);#### Composite Index (Multi-Column)
-- Leftmost prefix rule: Index can be used for:
-- (col1), (col1, col2), (col1, col2, col3)
-- But NOT for: (col2), (col3), (col2, col3)
CREATE INDEX idx_orders_user_status_created
ON orders(user_id, status, created_at);
-- This index can optimize:
✅ WHERE user_id = 123
✅ WHERE user_id = 123 AND status = 1
✅ WHERE user_id = 123 AND status = 1 AND created_at > '2025-01-01'
✅ WHERE user_id = 123 ORDER BY status, created_at
-- This index CANNOT optimize:
❌ WHERE status = 1 -- doesn't start with user_id
❌ WHERE created_at > '2025-01-01' -- doesn't start with user_id
❌ WHERE user_id = 123 AND created_at > '2025-01-01' -- skips status#### Covering Index
-- Index contains all columns needed for query (no table access needed)
CREATE INDEX idx_users_email_name_status
ON users(email, name, status);
-- This query only uses the index (no table lookup):
SELECT name, status FROM users WHERE email = '[email protected]';
-- EXPLAIN shows: Using index (no "Using where" = covering index)#### Index Pitfalls
-- 1. Function on indexed column
❌ WHERE DATE(created_at) = '2025-01-01' -- Index not used
✅ WHERE created_at >= '2025-01-01 00:00:00'
AND created_at < '2025-01-02 00:00:00'
-- 2. Implicit type conversion
❌ WHERE user_id = '123' -- user_id is INT, '123' is string
✅ WHERE user_id = 123
-- 3. Leading wildcard
❌ WHERE name LIKE '%john%' -- Index not used
✅ WHERE name LIKE 'john%' -- Index can be used
-- 4. OR conditions on different columns
❌ WHERE user_id = 123 OR email = '[email protected]' -- Index might not be used
✅ Use UNION instead:
(SELECT * FROM users WHERE user_id = 123)
UNION
(SELECT * FROM users WHERE email = '[email protected]')
-- 5. NOT conditions
❌ WHERE status != 1 -- May not use index
✅ WHERE status IN (2, 3, 4, 5) -- Better#### Data Type Selection
-- IDs
✅ BIGINT -- 8 bytes, range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
✅ BIGINT UNSIGNED -- 8 bytes, range: 0 to 18,446,744,073,709,551,615
❌ INT -- Only 4 bytes, may overflow with large data
-- Money/Decimal
✅ DECIMAL(10, 2) -- Exact precision, use for money
❌ FLOAT, DOUBLE -- Floating point errors, never use for money
-- Strings
✅ VARCHAR(n) -- Variable length, saves space
❌ CHAR(n) -- Fixed length, wastes space unless truly fixed
✅ TEXT -- For long text (up to 65,535 bytes)
✅ MEDIUMTEXT -- Up to 16MB
✅ LONGTEXT -- Up to 4GB
-- Dates and Times
✅ TIMESTAMP -- 4 bytes, UTC, range: 1970-2038 (Unix timestamp)
✅ DATETIME -- 8 bytes, no timezone, range: 1000-9999
✅ DATE -- 3 bytes, date only
✅ TIME -- 3 bytes, time only
-- Enums (Status Codes)
✅ TINYINT -- 1 byte, range: -128 to 127 or 0 to 255 (unsigned)
Use with comments: status TINYINT COMMENT '1:active, 2:inactive, 3:deleted'
❌ ENUM('active', 'inactive') -- Hard to change, avoid
-- Boolean
✅ TINYINT(1) -- MySQL standard for boolean
✅ BOOLEAN -- PostgreSQL has native boolean
-- JSON
✅ JSON (MySQL 5.7+) -- Native JSON type with validation
✅ JSONB (PostgreSQL) -- Binary JSON, indexed, fast
❌ TEXT + manual parse -- Inefficient, no validation
-- UUIDs
✅ BINARY(16) -- Efficient storage for UUID
✅ CHAR(36) -- Human-readable UUID string
❌ VARCHAR(36) -- Wastes space (fixed length UUID)#### Standard Table Structure
CREATE TABLE users (
-- Primary key
user_id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
-- Business columns
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
-- Status/flags
status TINYINT NOT NULL DEFAULT 1 COMMENT '1:active, 2:inactive, 3:deleted',
is_verified TINYINT(1) NOT NULL DEFAULT 0,
-- Timestamps (always include)
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Soft delete (optional)
deleted_at TIMESTAMP NULL DEFAULT NULL,
-- Indexes
INDEX idx_username (username),
INDEX idx_email (email),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
COMMENT='User table';#### Constraints
-- Primary Key
ALTER TABLE users ADD PRIMARY KEY (user_id);
-- Foreign Key (use with caution in large systems)
ALTER TABLE orders
ADD CONSTRAINT fk_orders_users
FOREIGN KEY (user_id) REFERENCES users(user_id)
ON DELETE RESTRICT -- Prevent deletion if referenced
ON UPDATE CASCADE; -- Update references if PK changes
-- Unique Constraint
ALTER TABLE users ADD UNIQUE KEY uk_email (email);
-- Check Constraint (MySQL 8.0+, PostgreSQL)
ALTER TABLE users ADD CONSTRAINT chk_age CHECK (age >= 0 AND age <= 150);
-- Default Value
ALTER TABLE users ALTER COLUMN status SET DEFAULT 1;Sharding strategies (Hash-Based, Range-Based, Consistent Hashing, Geographic, Challenges): see references/sharding-strategies.md Query optimization process (EXPLAIN analysis, index strategies, query rewriting): see references/query-optimization.md Data migration strategy and backup & recovery: see references/migration-backup.md
Ask these questions:
#### Data Requirements
#### Query Patterns
#### Non-Functional Requirements
#### Identify Entities
Example: E-commerce System
Entities:
- User
- Product
- Order
- OrderItem
- Category
- Review
- Payment
- Address
Attributes:
User: user_id, username, email, password_hash, created_at
Product: product_id, name, description, price, stock, category_id
Order: order_id, user_id, total_amount, status, created_at
OrderItem: item_id, order_id, product_id, quantity, unit_price#### Define Relationships
User 1----N Order (One user has many orders)
Order 1----N OrderItem (One order has many items)
Product 1----N OrderItem (One product in many orders)
Product N----1 Category (Many products in one category)
Product 1----N Review (One product has many reviews)
User 1----N Review (One user writes many reviews)
User 1----N Address (One user has many addresses)
Order 1----1 Payment (One order has one payment)#### Draw ER Diagram
[User] ──1:N── [Order] ──1:N── [OrderItem] ──N:1── [Product]
│ │ │
│ │ │
1 1 N
│ │ │
[Address] [Payment] [Category]
│ │
1 1
│ │
[Review] ──────────────────────────────────────────────┘Apply normalization rules (1NF → 2NF → 3NF), then evaluate if denormalization needed.
When helping with database design:
When a user asks for database design help:
Based on the answers, provide tailored, production-ready database designs.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.