| name | database |
| description | Database design, optimization, and management for SQL and NoSQL databases. Covers schema design, indexing, query optimization, migrations, and database best practices. Use when designing database schemas, optimizing queries, troubleshooting database performance, or implementing data models. |
Database Development
Schema design, optimization, and management best practices.
Schema Design
Normalization
CREATE TABLE orders (
id INT,
products VARCHAR(255)
);
CREATE TABLE orders (id INT PRIMARY KEY);
CREATE TABLE order_items (
order_id INT REFERENCES orders(id),
product_id INT REFERENCES products(id),
quantity INT
);
Data Types
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
uuid CHAR(36) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL,
status ENUM('active', 'inactive', 'banned'),
balance DECIMAL(10,2) NOT NULL DEFAULT 0,
metadata JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE events (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
data JSONB NOT NULL,
tags TEXT[] NOT NULL DEFAULT '{}',
tsv TSVECTOR,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Relationships
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL
);
CREATE TABLE post_tags (
post_id INT REFERENCES posts(id) ON DELETE CASCADE,
tag_id INT REFERENCES tags(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (post_id, tag_id)
);
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
bio TEXT,
avatar_url VARCHAR(255)
);
Indexing
Index Types
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';
CREATE INDEX idx_users_email_lower ON users(LOWER(email));
CREATE FULLTEXT INDEX idx_posts_content ON posts(title, content);
CREATE INDEX idx_events_data ON events USING GIN(data);
Index Strategy
SHOW INDEX FROM orders;
\d orders
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123;
Query Optimization
EXPLAIN Analysis
EXPLAIN SELECT * FROM orders
WHERE user_id = 123
AND created_at > '2024-01-01';
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE user_id = 123;
Common Optimizations
SELECT * FROM users WHERE id = 1;
SELECT id, name, email FROM users WHERE id = 1;
SELECT * FROM users WHERE email = 'a@b.com' OR name = 'John';
SELECT * FROM users WHERE email = 'a@b.com'
UNION ALL
SELECT * FROM users WHERE name = 'John' AND email != 'a@b.com';
SELECT * FROM users WHERE YEAR(created_at) = 2024;
SELECT * FROM users
WHERE created_at >=
created_at ;
products name ;
products
(name) AGAINST( MODE);
N+1 Problem
SELECT p.*, u.name as author_name
FROM posts p
JOIN users u ON p.user_id = u.id
LIMIT 10;
SELECT * FROM users
WHERE id IN (SELECT DISTINCT user_id FROM posts WHERE ...);
Migrations
Migration Best Practices
BEGIN;
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE INDEX CONCURRENTLY idx_users_phone ON users(phone);
ALTER TABLE users RENAME COLUMN phone TO phone_number;
COMMIT;
BEGIN;
ALTER TABLE users DROP COLUMN phone_number;
DROP INDEX idx_users_phone;
COMMIT;
Safe Migration Patterns
ALTER TABLE users ADD COLUMN status VARCHAR(20);
UPDATE users SET status = 'active' WHERE status IS NULL;
ALTER TABLE users ALTER COLUMN status SET NOT NULL;
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active';
CREATE TABLE accounts (LIKE users INCLUDING ALL);
INSERT INTO accounts SELECT * FROM users;
Performance
Connection Pooling
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
database: 'myapp',
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
Pagination
SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 10000;
SELECT * FROM posts
WHERE created_at < '2024-01-15 10:30:00'
ORDER BY created_at DESC
LIMIT 20;
SELECT * FROM posts
WHERE (created_at, id) < ('2024-01-15 10:30:00', 12345)
ORDER BY created_at DESC, id DESC
LIMIT 20;
Batch Operations
INSERT INTO logs (message) VALUES ('log1');
INSERT INTO logs (message) VALUES ('log2');
INSERT INTO logs (message) VALUES
('log1'),
('log2'),
('log3');
COPY logs (message) FROM '/path/to/file.csv' WITH CSV;
Transactions
ACID Properties
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
SELECT * FROM accounts WHERE id = 2 FOR UPDATE;
COMMIT;
NoSQL Patterns
Document Database (MongoDB)
{
_id: ObjectId("..."),
title: "Blog Post",
author: {
name: "John",
email: "john@example.com"
},
comments: [
{ text: "Great!", user: "Jane" }
]
}
{
_id: ObjectId("..."),
title: "Blog Post",
author_id: ObjectId("...")
}
db.posts.createIndex({ "author_id": 1 });
db.posts.createIndex({ "title": "text", "content": "text" });
Key-Value (Redis)
SET user:123 '{"name":"John"}' EX 3600
GET user:123
INCR page:views:homepage
GET page:views:homepage
INCR rate:ip:192.168.1.1
EXPIRE rate:ip:192.168.1.1 60
Backup & Recovery
mysqldump -u root -p database > backup.sql
mysql -u root -p database < backup.sql
pg_dump -Fc database > backup.dump
pg_restore -d database backup.dump
pg_basebackup -D /backup/base -Fp -Xs -P
Monitoring Queries
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SELECT pid, now() - pg_stat_activity.query_start AS duration, query
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY duration DESC;
SELECT
table_name,
pg_size_pretty(pg_total_relation_size(quote_ident(table_name)))
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY pg_total_relation_size(quote_ident(table_name)) DESC;