Search K
Appearance
Appearance
📊 SEO元描述:2024年最新数据模型设计教程,详解关系型和NoSQL数据库设计原则、范式理论、索引优化。包含完整设计案例,适合Node.js开发者掌握数据库架构技术。
核心关键词:数据模型设计2024、数据库架构设计、关系型数据库设计、NoSQL数据建模、数据库范式理论
长尾关键词:数据库设计最佳实践、数据模型设计原则、数据库表结构设计、NoSQL文档设计、数据库性能优化设计
通过本节数据模型设计教程,你将系统性掌握:
数据模型设计是什么?这是软件架构中的核心问题。数据模型设计是将现实世界的业务需求转换为数据库结构的过程,也是系统性能和可维护性的基础。
💡 设计理念:好的数据模型设计是系统成功的基石
让我们从关系型数据库的设计原则开始:
// 🎉 关系型数据库设计原则详解
// 1. 数据库范式理论实现
class DatabaseNormalization {
constructor() {
this.examples = {};
}
// 第一范式(1NF)- 原子性
demonstrateFirstNormalForm() {
// 违反1NF的设计
const violating1NF = {
tableName: 'users_bad',
structure: {
id: 'INT PRIMARY KEY',
name: 'VARCHAR(100)',
phones: 'VARCHAR(255)', // 存储多个电话号码,违反原子性
addresses: 'TEXT' // 存储多个地址,违反原子性
},
sampleData: [
{ id: 1, name: 'Alice', phones: '123-456-7890,098-765-4321', addresses: '北京市朝阳区;上海市浦东区' }
]
};
// 符合1NF的设计
const conforming1NF = {
users: {
structure: {
id: 'INT PRIMARY KEY',
name: 'VARCHAR(100)'
}
},
user_phones: {
structure: {
id: 'INT PRIMARY KEY',
user_id: 'INT',
phone: 'VARCHAR(20)',
type: 'ENUM("mobile", "home", "work")'
}
},
user_addresses: {
structure: {
id: 'INT PRIMARY KEY',
user_id: 'INT',
address: 'TEXT',
type: 'ENUM("home", "work", "other")'
}
}
};
return { violating1NF, conforming1NF };
}
// 第二范式(2NF)- 完全函数依赖
demonstrateSecondNormalForm() {
// 违反2NF的设计
const violating2NF = {
tableName: 'order_items_bad',
structure: {
order_id: 'INT',
product_id: 'INT',
product_name: 'VARCHAR(100)', // 部分依赖于product_id
product_price: 'DECIMAL(10,2)', // 部分依赖于product_id
quantity: 'INT',
PRIMARY_KEY: '(order_id, product_id)'
}
};
// 符合2NF的设计
const conforming2NF = {
orders: {
structure: {
id: 'INT PRIMARY KEY',
customer_id: 'INT',
order_date: 'TIMESTAMP',
total_amount: 'DECIMAL(10,2)'
}
},
products: {
structure: {
id: 'INT PRIMARY KEY',
name: 'VARCHAR(100)',
price: 'DECIMAL(10,2)',
description: 'TEXT'
}
},
order_items: {
structure: {
order_id: 'INT',
product_id: 'INT',
quantity: 'INT',
unit_price: 'DECIMAL(10,2)', // 订单时的价格
PRIMARY_KEY: '(order_id, product_id)'
}
}
};
return { violating2NF, conforming2NF };
}
// 第三范式(3NF)- 消除传递依赖
demonstrateThirdNormalForm() {
// 违反3NF的设计
const violating3NF = {
tableName: 'employees_bad',
structure: {
id: 'INT PRIMARY KEY',
name: 'VARCHAR(100)',
department_id: 'INT',
department_name: 'VARCHAR(100)', // 传递依赖
department_location: 'VARCHAR(100)' // 传递依赖
}
};
// 符合3NF的设计
const conforming3NF = {
employees: {
structure: {
id: 'INT PRIMARY KEY',
name: 'VARCHAR(100)',
department_id: 'INT',
position: 'VARCHAR(100)',
salary: 'DECIMAL(10,2)'
}
},
departments: {
structure: {
id: 'INT PRIMARY KEY',
name: 'VARCHAR(100)',
location: 'VARCHAR(100)',
manager_id: 'INT'
}
}
};
return { violating3NF, conforming3NF };
}
}
// 2. 实际业务场景的数据库设计
class ECommerceDataModel {
constructor() {
this.schema = this.designSchema();
}
designSchema() {
return {
// 用户相关表
users: {
id: 'INT AUTO_INCREMENT PRIMARY KEY',
username: 'VARCHAR(50) UNIQUE NOT NULL',
email: 'VARCHAR(100) UNIQUE NOT NULL',
password_hash: 'VARCHAR(255) NOT NULL',
first_name: 'VARCHAR(50)',
last_name: 'VARCHAR(50)',
phone: 'VARCHAR(20)',
date_of_birth: 'DATE',
gender: 'ENUM("male", "female", "other")',
status: 'ENUM("active", "inactive", "suspended") DEFAULT "active"',
email_verified: 'BOOLEAN DEFAULT FALSE',
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
indexes: [
'INDEX idx_username (username)',
'INDEX idx_email (email)',
'INDEX idx_status (status)',
'INDEX idx_created_at (created_at)'
]
},
// 用户地址表
user_addresses: {
id: 'INT AUTO_INCREMENT PRIMARY KEY',
user_id: 'INT NOT NULL',
type: 'ENUM("billing", "shipping", "both") DEFAULT "both"',
first_name: 'VARCHAR(50) NOT NULL',
last_name: 'VARCHAR(50) NOT NULL',
company: 'VARCHAR(100)',
address_line1: 'VARCHAR(255) NOT NULL',
address_line2: 'VARCHAR(255)',
city: 'VARCHAR(100) NOT NULL',
state: 'VARCHAR(100)',
postal_code: 'VARCHAR(20) NOT NULL',
country: 'VARCHAR(100) NOT NULL',
phone: 'VARCHAR(20)',
is_default: 'BOOLEAN DEFAULT FALSE',
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
foreign_keys: [
'FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE'
],
indexes: [
'INDEX idx_user_id (user_id)',
'INDEX idx_type (type)',
'INDEX idx_country (country)'
]
},
// 商品分类表
categories: {
id: 'INT AUTO_INCREMENT PRIMARY KEY',
name: 'VARCHAR(100) NOT NULL',
slug: 'VARCHAR(120) UNIQUE NOT NULL',
description: 'TEXT',
parent_id: 'INT NULL',
image_url: 'VARCHAR(255)',
sort_order: 'INT DEFAULT 0',
is_active: 'BOOLEAN DEFAULT TRUE',
meta_title: 'VARCHAR(255)',
meta_description: 'TEXT',
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
foreign_keys: [
'FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL'
],
indexes: [
'INDEX idx_slug (slug)',
'INDEX idx_parent_id (parent_id)',
'INDEX idx_is_active (is_active)',
'INDEX idx_sort_order (sort_order)'
]
},
// 商品表
products: {
id: 'INT AUTO_INCREMENT PRIMARY KEY',
name: 'VARCHAR(255) NOT NULL',
slug: 'VARCHAR(275) UNIQUE NOT NULL',
description: 'TEXT',
short_description: 'VARCHAR(500)',
sku: 'VARCHAR(100) UNIQUE NOT NULL',
price: 'DECIMAL(10,2) NOT NULL',
compare_price: 'DECIMAL(10,2)',
cost_price: 'DECIMAL(10,2)',
weight: 'DECIMAL(8,2)',
dimensions: 'JSON', // {length, width, height}
category_id: 'INT NOT NULL',
brand_id: 'INT',
status: 'ENUM("active", "inactive", "draft") DEFAULT "draft"',
featured: 'BOOLEAN DEFAULT FALSE',
digital: 'BOOLEAN DEFAULT FALSE',
track_inventory: 'BOOLEAN DEFAULT TRUE',
inventory_quantity: 'INT DEFAULT 0',
low_stock_threshold: 'INT DEFAULT 10',
images: 'JSON', // 图片URL数组
attributes: 'JSON', // 商品属性
seo_title: 'VARCHAR(255)',
seo_description: 'TEXT',
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
foreign_keys: [
'FOREIGN KEY (category_id) REFERENCES categories(id)',
'FOREIGN KEY (brand_id) REFERENCES brands(id) ON DELETE SET NULL'
],
indexes: [
'INDEX idx_slug (slug)',
'INDEX idx_sku (sku)',
'INDEX idx_category_id (category_id)',
'INDEX idx_brand_id (brand_id)',
'INDEX idx_status (status)',
'INDEX idx_featured (featured)',
'INDEX idx_price (price)',
'FULLTEXT idx_search (name, description, short_description)'
]
},
// 商品变体表(用于处理不同规格的商品)
product_variants: {
id: 'INT AUTO_INCREMENT PRIMARY KEY',
product_id: 'INT NOT NULL',
sku: 'VARCHAR(100) UNIQUE NOT NULL',
price: 'DECIMAL(10,2)',
compare_price: 'DECIMAL(10,2)',
cost_price: 'DECIMAL(10,2)',
inventory_quantity: 'INT DEFAULT 0',
weight: 'DECIMAL(8,2)',
option1: 'VARCHAR(100)', // 如:颜色
option2: 'VARCHAR(100)', // 如:尺寸
option3: 'VARCHAR(100)', // 如:材质
image_url: 'VARCHAR(255)',
is_active: 'BOOLEAN DEFAULT TRUE',
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
foreign_keys: [
'FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE'
],
indexes: [
'INDEX idx_product_id (product_id)',
'INDEX idx_sku (sku)',
'INDEX idx_is_active (is_active)'
]
},
// 订单表
orders: {
id: 'INT AUTO_INCREMENT PRIMARY KEY',
order_number: 'VARCHAR(50) UNIQUE NOT NULL',
user_id: 'INT',
email: 'VARCHAR(100) NOT NULL',
status: 'ENUM("pending", "processing", "shipped", "delivered", "cancelled", "refunded") DEFAULT "pending"',
payment_status: 'ENUM("pending", "paid", "failed", "refunded") DEFAULT "pending"',
fulfillment_status: 'ENUM("unfulfilled", "partial", "fulfilled") DEFAULT "unfulfilled"',
// 金额信息
subtotal: 'DECIMAL(10,2) NOT NULL',
tax_amount: 'DECIMAL(10,2) DEFAULT 0',
shipping_amount: 'DECIMAL(10,2) DEFAULT 0',
discount_amount: 'DECIMAL(10,2) DEFAULT 0',
total_amount: 'DECIMAL(10,2) NOT NULL',
// 地址信息(冗余存储,保证历史数据完整性)
billing_address: 'JSON',
shipping_address: 'JSON',
// 其他信息
notes: 'TEXT',
currency: 'VARCHAR(3) DEFAULT "CNY"',
payment_method: 'VARCHAR(50)',
shipping_method: 'VARCHAR(50)',
// 时间戳
order_date: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
shipped_date: 'TIMESTAMP NULL',
delivered_date: 'TIMESTAMP NULL',
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
foreign_keys: [
'FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL'
],
indexes: [
'INDEX idx_order_number (order_number)',
'INDEX idx_user_id (user_id)',
'INDEX idx_status (status)',
'INDEX idx_payment_status (payment_status)',
'INDEX idx_order_date (order_date)',
'INDEX idx_email (email)'
]
},
// 订单项表
order_items: {
id: 'INT AUTO_INCREMENT PRIMARY KEY',
order_id: 'INT NOT NULL',
product_id: 'INT NOT NULL',
variant_id: 'INT',
quantity: 'INT NOT NULL',
unit_price: 'DECIMAL(10,2) NOT NULL',
total_price: 'DECIMAL(10,2) NOT NULL',
// 冗余存储商品信息(保证历史数据完整性)
product_name: 'VARCHAR(255) NOT NULL',
product_sku: 'VARCHAR(100) NOT NULL',
product_image: 'VARCHAR(255)',
variant_options: 'JSON', // 变体选项
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
foreign_keys: [
'FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE',
'FOREIGN KEY (product_id) REFERENCES products(id)',
'FOREIGN KEY (variant_id) REFERENCES product_variants(id) ON DELETE SET NULL'
],
indexes: [
'INDEX idx_order_id (order_id)',
'INDEX idx_product_id (product_id)',
'INDEX idx_variant_id (variant_id)'
]
}
};
}
// 生成创建表的SQL语句
generateCreateTableSQL() {
const sqlStatements = [];
Object.entries(this.schema).forEach(([tableName, tableSchema]) => {
let sql = `CREATE TABLE ${tableName} (\n`;
// 添加字段定义
const fields = [];
Object.entries(tableSchema).forEach(([fieldName, fieldDef]) => {
if (!['foreign_keys', 'indexes'].includes(fieldName)) {
fields.push(` ${fieldName} ${fieldDef}`);
}
});
sql += fields.join(',\n');
// 添加外键约束
if (tableSchema.foreign_keys) {
sql += ',\n ' + tableSchema.foreign_keys.join(',\n ');
}
sql += '\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n';
sqlStatements.push(sql);
// 添加索引
if (tableSchema.indexes) {
tableSchema.indexes.forEach(index => {
sqlStatements.push(`ALTER TABLE ${tableName} ADD ${index};`);
});
}
});
return sqlStatements;
}
}
console.log('关系型数据库设计原则完成');// 💎 NoSQL数据建模详解
const mongoose = require('mongoose');
// 1. MongoDB数据建模策略
class MongoDBDataModeling {
constructor() {
this.designPatterns = {};
}
// 嵌入式文档 vs 引用文档的选择
demonstrateEmbeddingVsReferencing() {
// 嵌入式设计 - 适用于一对少量的关系
const embeddedDesign = {
name: 'User with Embedded Addresses',
schema: {
_id: 'ObjectId',
username: 'String',
email: 'String',
profile: {
firstName: 'String',
lastName: 'String',
avatar: 'String',
bio: 'String'
},
addresses: [
{
type: 'String', // home, work, other
street: 'String',
city: 'String',
state: 'String',
zipCode: 'String',
country: 'String',
isDefault: 'Boolean'
}
],
preferences: {
language: 'String',
timezone: 'String',
notifications: {
email: 'Boolean',
sms: 'Boolean',
push: 'Boolean'
}
},
createdAt: 'Date',
updatedAt: 'Date'
},
advantages: [
'单次查询获取所有数据',
'原子性更新',
'更好的数据局部性'
],
disadvantages: [
'文档大小限制(16MB)',
'数据冗余',
'更新复杂性'
]
};
// 引用设计 - 适用于一对多的关系
const referencedDesign = {
name: 'User with Referenced Posts',
userSchema: {
_id: 'ObjectId',
username: 'String',
email: 'String',
profile: {
firstName: 'String',
lastName: 'String'
},
createdAt: 'Date'
},
postSchema: {
_id: 'ObjectId',
title: 'String',
content: 'String',
authorId: 'ObjectId', // 引用用户ID
tags: ['String'],
status: 'String',
publishedAt: 'Date',
createdAt: 'Date'
},
advantages: [
'避免文档大小限制',
'减少数据冗余',
'灵活的查询'
],
disadvantages: [
'需要多次查询或使用$lookup',
'无法保证引用完整性',
'复杂的事务处理'
]
};
return { embeddedDesign, referencedDesign };
}
// 实际的MongoDB Schema设计
designBlogSchema() {
// 用户Schema - 嵌入式设计
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3,
maxlength: 30
},
email: {
type: String,
required: true,
unique: true,
lowercase: true
},
password: {
type: String,
required: true,
select: false
},
profile: {
firstName: String,
lastName: String,
avatar: String,
bio: {
type: String,
maxlength: 500
},
website: String,
location: String,
socialLinks: {
twitter: String,
github: String,
linkedin: String
}
},
settings: {
theme: {
type: String,
enum: ['light', 'dark'],
default: 'light'
},
language: {
type: String,
default: 'zh-CN'
},
notifications: {
email: { type: Boolean, default: true },
browser: { type: Boolean, default: true },
mobile: { type: Boolean, default: false }
},
privacy: {
profileVisible: { type: Boolean, default: true },
emailVisible: { type: Boolean, default: false }
}
},
stats: {
postsCount: { type: Number, default: 0 },
followersCount: { type: Number, default: 0 },
followingCount: { type: Number, default: 0 },
likesReceived: { type: Number, default: 0 }
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'],
default: 'user'
},
status: {
type: String,
enum: ['active', 'inactive', 'suspended'],
default: 'active'
},
lastLogin: Date,
emailVerified: { type: Boolean, default: false },
emailVerificationToken: String,
passwordResetToken: String,
passwordResetExpires: Date
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// 文章Schema - 混合设计
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
maxlength: 200
},
slug: {
type: String,
required: true,
unique: true,
lowercase: true
},
content: {
type: String,
required: true
},
excerpt: {
type: String,
maxlength: 500
},
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
username: String, // 冗余存储,提高查询性能
avatar: String // 冗余存储
},
category: {
type: String,
required: true,
enum: ['tech', 'lifestyle', 'travel', 'food', 'other']
},
tags: [{
type: String,
trim: true,
lowercase: true
}],
featuredImage: {
url: String,
alt: String,
caption: String
},
status: {
type: String,
enum: ['draft', 'published', 'archived'],
default: 'draft'
},
visibility: {
type: String,
enum: ['public', 'private', 'unlisted'],
default: 'public'
},
seo: {
metaTitle: String,
metaDescription: String,
keywords: [String]
},
stats: {
views: { type: Number, default: 0 },
likes: { type: Number, default: 0 },
shares: { type: Number, default: 0 },
comments: { type: Number, default: 0 }
},
publishedAt: Date,
featured: { type: Boolean, default: false },
allowComments: { type: Boolean, default: true }
}, {
timestamps: true,
toJSON: { virtuals: true }
});
// 评论Schema - 嵌入式设计
const commentSchema = new mongoose.Schema({
postId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
required: true
},
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
username: String,
avatar: String,
isGuest: { type: Boolean, default: false },
guestName: String,
guestEmail: String
},
content: {
type: String,
required: true,
maxlength: 1000
},
parentId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
},
replies: [{
author: {
id: mongoose.Schema.Types.ObjectId,
username: String,
avatar: String
},
content: {
type: String,
maxlength: 500
},
createdAt: { type: Date, default: Date.now },
likes: { type: Number, default: 0 }
}],
likes: { type: Number, default: 0 },
status: {
type: String,
enum: ['approved', 'pending', 'spam', 'deleted'],
default: 'approved'
},
ipAddress: String,
userAgent: String
}, {
timestamps: true
});
// 添加索引
userSchema.index({ username: 1 });
userSchema.index({ email: 1 });
userSchema.index({ 'stats.postsCount': -1 });
userSchema.index({ createdAt: -1 });
postSchema.index({ slug: 1 });
postSchema.index({ 'author.id': 1 });
postSchema.index({ status: 1, publishedAt: -1 });
postSchema.index({ category: 1, publishedAt: -1 });
postSchema.index({ tags: 1 });
postSchema.index({ featured: 1, publishedAt: -1 });
postSchema.index({ title: 'text', content: 'text' }); // 全文搜索
commentSchema.index({ postId: 1, createdAt: -1 });
commentSchema.index({ 'author.id': 1 });
commentSchema.index({ parentId: 1 });
return { userSchema, postSchema, commentSchema };
}
// 数据建模最佳实践
getBestPractices() {
return {
embedding: {
when: [
'一对一关系',
'一对少量关系(<100)',
'数据经常一起查询',
'子文档不会独立查询',
'数据更新不频繁'
],
examples: [
'用户和个人资料',
'订单和订单项',
'文章和元数据'
]
},
referencing: {
when: [
'一对多关系(>100)',
'多对多关系',
'数据会独立查询',
'数据更新频繁',
'需要数据一致性'
],
examples: [
'用户和文章',
'文章和评论',
'用户和关注关系'
]
},
hybrid: {
when: [
'需要平衡查询性能和数据一致性',
'某些字段需要冗余存储',
'复杂的业务场景'
],
examples: [
'文章中存储作者的基本信息',
'评论中存储用户头像',
'订单中存储商品快照'
]
}
};
}
}
// 2. 键值数据库设计(Redis)
class RedisDataModeling {
constructor() {
this.patterns = {};
}
// Redis数据结构设计模式
designRedisPatterns() {
return {
// 缓存模式
caching: {
userCache: {
key: 'user:{userId}',
type: 'hash',
fields: {
username: 'string',
email: 'string',
firstName: 'string',
lastName: 'string',
avatar: 'string'
},
ttl: 3600 // 1小时
},
postCache: {
key: 'post:{postId}',
type: 'string',
value: 'JSON serialized post data',
ttl: 1800 // 30分钟
}
},
// 会话存储
session: {
key: 'session:{sessionId}',
type: 'hash',
fields: {
userId: 'string',
username: 'string',
role: 'string',
lastActivity: 'timestamp',
ipAddress: 'string'
},
ttl: 86400 // 24小时
},
// 计数器
counters: {
postViews: {
key: 'post:views:{postId}',
type: 'string',
operations: ['INCR', 'GET']
},
userStats: {
key: 'user:stats:{userId}',
type: 'hash',
fields: {
postsCount: 'number',
followersCount: 'number',
likesReceived: 'number'
}
}
},
// 排行榜
leaderboards: {
topPosts: {
key: 'leaderboard:posts:views',
type: 'sorted_set',
score: 'view_count',
member: 'post_id'
},
activeUsers: {
key: 'leaderboard:users:activity',
type: 'sorted_set',
score: 'activity_score',
member: 'user_id'
}
},
// 队列
queues: {
emailQueue: {
key: 'queue:email',
type: 'list',
operations: ['LPUSH', 'RPOP'],
data: 'JSON serialized email job'
},
imageProcessing: {
key: 'queue:image:processing',
type: 'list',
operations: ['LPUSH', 'BRPOP'],
data: 'JSON serialized image job'
}
},
// 实时数据
realtime: {
onlineUsers: {
key: 'online:users',
type: 'set',
members: 'user_ids',
ttl: 300 // 5分钟
},
notifications: {
key: 'notifications:{userId}',
type: 'list',
maxLength: 100,
data: 'JSON serialized notification'
}
}
};
}
// Redis键命名规范
getKeyNamingConventions() {
return {
general: [
'使用冒号(:)分隔命名空间',
'使用小写字母和下划线',
'保持键名简洁但有意义',
'使用一致的命名模式'
],
patterns: {
entity: 'entity_type:id',
relationship: 'entity1:relationship:entity2',
index: 'index:field:value',
cache: 'cache:entity:id',
session: 'session:id',
queue: 'queue:name',
counter: 'counter:entity:field',
lock: 'lock:resource:id'
},
examples: [
'user:12345',
'post:comments:67890',
'index:email:user@example.com',
'cache:user:12345',
'session:abc123def456',
'queue:email_notifications',
'counter:post:views:67890',
'lock:user:update:12345'
]
};
}
}
console.log('NoSQL数据建模设计完成');NoSQL数据建模要点:
// 🚀 索引设计策略详解
// 1. MySQL索引设计策略
class MySQLIndexStrategy {
constructor() {
this.indexTypes = {};
this.optimizationRules = {};
}
// 索引类型和使用场景
getIndexTypes() {
return {
primary: {
description: '主键索引',
characteristics: ['唯一性', '非空', '聚簇索引'],
usage: '每个表只能有一个主键索引',
example: 'PRIMARY KEY (id)'
},
unique: {
description: '唯一索引',
characteristics: ['唯一性', '可以为NULL'],
usage: '保证数据唯一性,如邮箱、用户名',
example: 'UNIQUE KEY uk_email (email)'
},
normal: {
description: '普通索引',
characteristics: ['最常用', '提高查询速度'],
usage: '经常用于WHERE、ORDER BY的字段',
example: 'KEY idx_created_at (created_at)'
},
composite: {
description: '复合索引',
characteristics: ['多个字段组合', '最左前缀原则'],
usage: '多字段查询条件',
example: 'KEY idx_user_status (user_id, status, created_at)'
},
fulltext: {
description: '全文索引',
characteristics: ['文本搜索', 'MyISAM和InnoDB支持'],
usage: '全文搜索功能',
example: 'FULLTEXT KEY ft_content (title, content)'
},
spatial: {
description: '空间索引',
characteristics: ['地理位置数据', '几何数据类型'],
usage: 'GIS应用',
example: 'SPATIAL KEY sp_location (location)'
}
};
}
// 索引设计最佳实践
getIndexDesignPrinciples() {
return {
selectivity: {
principle: '选择性原则',
description: '索引的选择性越高,效果越好',
calculation: '选择性 = 不同值的数量 / 总记录数',
goodExample: 'email字段(选择性接近1)',
badExample: 'gender字段(选择性很低)'
},
leftmostPrefix: {
principle: '最左前缀原则',
description: '复合索引从最左边的字段开始匹配',
example: {
index: '(a, b, c)',
canUse: ['a', 'a,b', 'a,b,c'],
cannotUse: ['b', 'c', 'b,c']
}
},
orderMatters: {
principle: '字段顺序很重要',
description: '将选择性高的字段放在前面',
example: {
better: '(user_id, status, created_at)',
worse: '(status, user_id, created_at)'
}
},
coveringIndex: {
principle: '覆盖索引',
description: '索引包含查询所需的所有字段',
benefit: '避免回表查询,提高性能',
example: 'SELECT user_id, status FROM orders WHERE user_id = ? AND status = ?'
}
};
}
// 实际索引设计案例
designIndexesForECommerce() {
return {
users: [
'PRIMARY KEY (id)',
'UNIQUE KEY uk_username (username)',
'UNIQUE KEY uk_email (email)',
'KEY idx_status (status)',
'KEY idx_created_at (created_at)',
'KEY idx_last_login (last_login)'
],
products: [
'PRIMARY KEY (id)',
'UNIQUE KEY uk_sku (sku)',
'KEY idx_category_status (category_id, status)',
'KEY idx_price (price)',
'KEY idx_featured_status (featured, status, created_at)',
'FULLTEXT KEY ft_search (name, description)',
'KEY idx_inventory (track_inventory, inventory_quantity)'
],
orders: [
'PRIMARY KEY (id)',
'UNIQUE KEY uk_order_number (order_number)',
'KEY idx_user_status (user_id, status)',
'KEY idx_status_date (status, order_date)',
'KEY idx_payment_status (payment_status)',
'KEY idx_email (email)',
'KEY idx_order_date (order_date)'
],
order_items: [
'PRIMARY KEY (id)',
'KEY idx_order_id (order_id)',
'KEY idx_product_id (product_id)',
'KEY idx_order_product (order_id, product_id)'
]
};
}
// 索引性能分析
analyzeIndexPerformance() {
return {
explainColumns: {
id: '查询的标识符',
select_type: '查询类型(SIMPLE、PRIMARY、SUBQUERY等)',
table: '查询的表',
partitions: '匹配的分区',
type: '连接类型(system、const、eq_ref、ref、range、index、ALL)',
possible_keys: '可能使用的索引',
key: '实际使用的索引',
key_len: '使用的索引长度',
ref: '与索引比较的列',
rows: '估计要检查的行数',
filtered: '按表条件过滤的行百分比',
Extra: '额外信息'
},
goodSigns: [
'type为const、eq_ref、ref',
'key显示使用了索引',
'rows数量较少',
'Extra中没有Using filesort、Using temporary'
],
badSigns: [
'type为ALL(全表扫描)',
'key为NULL(没有使用索引)',
'rows数量很大',
'Extra中有Using filesort、Using temporary'
]
};
}
// 索引维护策略
getIndexMaintenanceStrategy() {
return {
monitoring: [
'定期检查慢查询日志',
'监控索引使用情况',
'分析查询执行计划',
'检查索引碎片'
],
optimization: [
'删除未使用的索引',
'合并重复的索引',
'调整复合索引字段顺序',
'添加缺失的索引'
],
tools: [
'EXPLAIN分析查询',
'SHOW INDEX查看索引信息',
'pt-index-usage分析索引使用',
'pt-duplicate-key-checker检查重复索引'
]
};
}
}
// 2. MongoDB索引设计策略
class MongoDBIndexStrategy {
constructor() {
this.indexTypes = {};
}
// MongoDB索引类型
getMongoDBIndexTypes() {
return {
single: {
description: '单字段索引',
syntax: 'db.collection.createIndex({ field: 1 })',
example: 'db.users.createIndex({ email: 1 })'
},
compound: {
description: '复合索引',
syntax: 'db.collection.createIndex({ field1: 1, field2: -1 })',
example: 'db.posts.createIndex({ authorId: 1, publishedAt: -1 })'
},
multikey: {
description: '多键索引',
description_detail: '自动为数组字段创建',
example: 'db.posts.createIndex({ tags: 1 })'
},
text: {
description: '文本索引',
syntax: 'db.collection.createIndex({ field: "text" })',
example: 'db.posts.createIndex({ title: "text", content: "text" })'
},
geospatial: {
description: '地理空间索引',
types: ['2d', '2dsphere'],
example: 'db.places.createIndex({ location: "2dsphere" })'
},
hashed: {
description: '哈希索引',
usage: '分片键',
example: 'db.users.createIndex({ _id: "hashed" })'
},
partial: {
description: '部分索引',
syntax: 'db.collection.createIndex({ field: 1 }, { partialFilterExpression: { condition } })',
example: 'db.users.createIndex({ email: 1 }, { partialFilterExpression: { email: { $exists: true } } })'
},
sparse: {
description: '稀疏索引',
usage: '只索引存在该字段的文档',
example: 'db.users.createIndex({ phone: 1 }, { sparse: true })'
}
};
}
// MongoDB索引设计案例
designMongoDBIndexes() {
return {
users: [
'{ username: 1 }',
'{ email: 1 }',
'{ "profile.firstName": 1, "profile.lastName": 1 }',
'{ createdAt: -1 }',
'{ role: 1, status: 1 }'
],
posts: [
'{ slug: 1 }',
'{ "author.id": 1 }',
'{ status: 1, publishedAt: -1 }',
'{ category: 1, publishedAt: -1 }',
'{ tags: 1 }',
'{ featured: 1, publishedAt: -1 }',
'{ title: "text", content: "text" }'
],
comments: [
'{ postId: 1, createdAt: -1 }',
'{ "author.id": 1 }',
'{ parentId: 1 }',
'{ status: 1 }'
]
};
}
// MongoDB索引优化技巧
getOptimizationTips() {
return {
queryPatterns: [
'分析查询模式,为常用查询创建索引',
'使用explain()分析查询性能',
'考虑查询的选择性和频率'
],
compoundIndexes: [
'遵循ESR原则:Equality, Sort, Range',
'将等值查询字段放在前面',
'排序字段放在中间',
'范围查询字段放在最后'
],
indexIntersection: [
'MongoDB可以使用多个单字段索引',
'但复合索引通常性能更好',
'避免过多的单字段索引'
],
maintenance: [
'定期检查索引使用情况',
'删除未使用的索引',
'监控索引大小和内存使用',
'考虑索引对写入性能的影响'
]
};
}
}
// 3. 索引性能测试工具
class IndexPerformanceTesting {
constructor() {
this.testCases = {};
}
// MySQL索引性能测试
generateMySQLPerformanceTest() {
return {
setup: `
-- 创建测试表
CREATE TABLE test_users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
status ENUM('active', 'inactive'),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP NULL
);
-- 插入测试数据
INSERT INTO test_users (username, email, status, last_login)
SELECT
CONCAT('user', n),
CONCAT('user', n, '@example.com'),
IF(RAND() > 0.8, 'inactive', 'active'),
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365) DAY)
FROM (
SELECT a.N + b.N * 10 + c.N * 100 + d.N * 1000 + 1 n
FROM
(SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a,
(SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b,
(SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c,
(SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) d
) numbers
WHERE n <= 100000;
`,
testQueries: [
{
name: '无索引查询',
query: 'SELECT * FROM test_users WHERE email = "user50000@example.com"',
expectedType: 'ALL'
},
{
name: '添加索引后查询',
setup: 'CREATE INDEX idx_email ON test_users(email)',
query: 'SELECT * FROM test_users WHERE email = "user50000@example.com"',
expectedType: 'ref'
},
{
name: '复合索引查询',
setup: 'CREATE INDEX idx_status_created ON test_users(status, created_at)',
query: 'SELECT * FROM test_users WHERE status = "active" ORDER BY created_at DESC LIMIT 10',
expectedType: 'ref'
}
],
analysis: `
-- 分析查询性能
EXPLAIN SELECT * FROM test_users WHERE email = 'user50000@example.com';
-- 查看索引使用情况
SHOW INDEX FROM test_users;
-- 检查索引统计信息
SELECT
TABLE_NAME,
INDEX_NAME,
CARDINALITY,
SUB_PART,
PACKED,
NULLABLE,
INDEX_TYPE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'test_users';
`
};
}
// MongoDB索引性能测试
generateMongoDBPerformanceTest() {
return {
setup: `
// 创建测试集合并插入数据
use testdb;
// 生成测试数据
var users = [];
for (var i = 1; i <= 100000; i++) {
users.push({
username: "user" + i,
email: "user" + i + "@example.com",
status: Math.random() > 0.8 ? "inactive" : "active",
profile: {
firstName: "First" + i,
lastName: "Last" + i
},
tags: ["tag" + (i % 10), "tag" + (i % 20)],
createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000)
});
if (users.length === 1000) {
db.testUsers.insertMany(users);
users = [];
}
}
if (users.length > 0) {
db.testUsers.insertMany(users);
}
`,
testQueries: [
{
name: '无索引查询',
query: 'db.testUsers.find({ email: "user50000@example.com" }).explain("executionStats")',
expectedStage: 'COLLSCAN'
},
{
name: '单字段索引',
setup: 'db.testUsers.createIndex({ email: 1 })',
query: 'db.testUsers.find({ email: "user50000@example.com" }).explain("executionStats")',
expectedStage: 'IXSCAN'
},
{
name: '复合索引查询',
setup: 'db.testUsers.createIndex({ status: 1, createdAt: -1 })',
query: 'db.testUsers.find({ status: "active" }).sort({ createdAt: -1 }).limit(10).explain("executionStats")',
expectedStage: 'IXSCAN'
}
],
analysis: `
// 查看索引信息
db.testUsers.getIndexes();
// 分析索引使用情况
db.testUsers.aggregate([
{ $indexStats: {} }
]);
// 查看集合统计信息
db.testUsers.stats();
`
};
}
}
console.log('索引设计策略完成');索引设计要点:
通过本节数据模型设计教程的学习,你已经掌握:
A: 反范式化时机:1)查询性能要求极高;2)读多写少的场景;3)数据一致性要求不严格;4)需要减少表连接;5)分布式环境下的数据本地化。但要权衡数据一致性和维护复杂度。
A: 选择依据:1)数据结构复杂度(关系型vs文档型);2)事务要求(ACID vs最终一致性);3)扩展性需求(垂直vs水平扩展);4)查询复杂度;5)团队技能和运维能力;6)性能要求。
A: 不是。索引的代价:1)占用额外存储空间;2)降低写入性能;3)增加维护成本。应该:1)只为常用查询创建索引;2)定期检查索引使用情况;3)删除未使用的索引;4)合并重复索引。
A: 演化策略:1)使用数据库迁移工具;2)向后兼容的字段添加;3)渐进式重构;4)版本化API设计;5)数据转换脚本;6)灰度发布;7)回滚计划。
A: 一致性策略:1)选择合适的一致性级别;2)使用分布式事务(2PC、Saga);3)事件驱动架构;4)最终一致性设计;5)补偿机制;6)幂等性设计;7)监控和告警。
"数据模型设计是系统架构的基石,好的数据模型设计能够支撑业务的长期发展。掌握了数据建模技术,你就具备了构建可扩展、高性能系统的重要能力。继续学习数据库性能优化,完善你的数据库技能体系!"