feat: 更新设计文档

This commit is contained in:
R524809
2025-11-11 12:56:49 +08:00
parent 8eb49f02b1
commit e71b5e5bc0
9 changed files with 4936 additions and 144 deletions

View File

@@ -0,0 +1,832 @@
# 思投录数据库设计文档
## 一、数据库选型分析
### 1.1 业务特点分析
根据业务需求,系统需要处理以下类型的数据:
1. **结构化数据**
- 用户账户、持仓、交易记录
- 交易计划、计划步骤
- 资产净值、收益率等数值数据
2. **时间序列数据**
- 每日单位净值记录
- 资产快照(每日)
- 价格历史数据
3. **文本内容数据**
- 交易思考(交易记录时填写)
- 复盘内容(定期复盘时填写)
- 计划思考(创建计划时填写)
4. **关联关系**
- 交易记录与持仓的关联
- 交易计划与交易记录的关联
- 复盘与交易记录的关联
- 计划步骤与计划的关联
5. **查询需求**
- 复杂的时间范围查询
- 多表关联查询
- 聚合统计查询
- 时间线查询(按时间排序)
### 1.2 数据库选型推荐
#### 推荐方案PostgreSQL
**推荐理由:**
1. **关系型数据库优势**
- ✅ 数据结构清晰,关系明确
- ✅ 支持复杂查询和统计JOIN、聚合函数
- ✅ 支持事务,保证数据一致性
- ✅ ACID特性数据可靠性高
2. **PostgreSQL特有优势**
- ✅ 支持JSON/JSONB类型可灵活存储思考内容等文本
- ✅ 支持数组类型,适合存储计划步骤等
- ✅ 强大的时间序列查询能力
- ✅ 支持全文搜索(可用于搜索思考内容)
- ✅ 性能优秀,适合中小型应用
- ✅ 开源免费,社区活跃
3. **其他考虑**
- 如果团队更熟悉MySQLMySQL 8.0+ 也是不错的选择
- 对于时间序列数据可以考虑用TimescaleDB基于PostgreSQL的扩展
#### 备选方案MySQL 8.0+
**适用场景:**
- 团队更熟悉MySQL
- 需要更好的云服务支持如阿里云RDS
- 数据量不是特别大
**MySQL优势**
- ✅ 生态成熟,工具丰富
- ✅ 云服务支持好
- ✅ 性能稳定
- ⚠️ JSON支持不如PostgreSQL强大
- ⚠️ 复杂查询性能略逊于PostgreSQL
#### 不推荐方案
- **MongoDB等NoSQL**虽然可以存储JSON但关联查询复杂不适合这种关系明确的数据结构
- **Redis**:只适合缓存,不适合持久化存储
- **SQLite**:适合单机应用,不支持多用户并发
### 1.3 最终推荐
**推荐使用 PostgreSQL 14+**
理由:
1. 数据结构关系明确,适合关系型数据库
2. JSONB类型可以灵活存储思考内容
3. 强大的查询能力,适合复杂统计
4. 时间序列查询性能好
5. 支持全文搜索,方便搜索思考内容
---
## 二、数据库表设计
### 2.1 表结构总览
```
用户相关
├── users (用户表)
└── user_settings (用户设置表)
账户相关
├── accounts (账户表)
├── positions (持仓表)
├── cash_accounts (现金账户表)
└── position_warnings (持仓预警表)
交易相关
├── transactions (交易记录表)
├── transaction_thoughts (交易思考表)
└── transaction_reviews (交易复盘表)
计划相关
├── trading_plans (交易计划表)
├── plan_steps (计划步骤表)
└── plan_thoughts (计划思考表)
净值相关
├── net_value_snapshots (净值快照表)
└── daily_net_values (每日净值表)
市场数据
└── market_prices (市场价格表)
```
### 2.2 详细表结构设计
#### 2.2.1 用户相关表
##### users (用户表)
```sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20) UNIQUE,
password_hash VARCHAR(255) NOT NULL,
nickname VARCHAR(50),
avatar_url VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP,
status VARCHAR(20) DEFAULT 'active' -- active, inactive, deleted
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_phone ON users(phone);
CREATE INDEX idx_users_status ON users(status);
```
##### user_settings (用户设置表)
```sql
CREATE TABLE user_settings (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
setting_key VARCHAR(50) NOT NULL,
setting_value TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, setting_key)
);
CREATE INDEX idx_user_settings_user_id ON user_settings(user_id);
```
**常用设置项:**
- `position_limit_per_stock`: 单只股票仓位上限(百分比)
- `currency`: 默认货币
- `theme`: 主题设置
- `notification_enabled`: 是否启用通知
#### 2.2.2 账户相关表
##### accounts (账户表)
```sql
CREATE TABLE accounts (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL, -- stock, fund, cash, mixed
currency VARCHAR(10) NOT NULL DEFAULT 'CNY', -- CNY, USD, HKD
description TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'active' -- active, archived, deleted
);
CREATE INDEX idx_accounts_user_id ON accounts(user_id);
CREATE INDEX idx_accounts_status ON accounts(status);
```
##### positions (持仓表)
```sql
CREATE TABLE positions (
id BIGSERIAL PRIMARY KEY,
account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
symbol VARCHAR(20) NOT NULL, -- 股票/基金代码
name VARCHAR(100) NOT NULL, -- 股票/基金名称
market VARCHAR(20) NOT NULL, -- A股, 港股, 美股
shares DECIMAL(18, 4) NOT NULL DEFAULT 0, -- 持仓份额
cost_price DECIMAL(18, 4) NOT NULL, -- 成本价(每股/每份)
current_price DECIMAL(18, 4), -- 当前价格
currency VARCHAR(10) NOT NULL DEFAULT 'CNY',
status VARCHAR(20) DEFAULT 'active', -- active, suspended, delisted
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(account_id, symbol, market) -- 同一账户同一股票只能有一条持仓
);
CREATE INDEX idx_positions_account_id ON positions(account_id);
CREATE INDEX idx_positions_symbol ON positions(symbol);
CREATE INDEX idx_positions_status ON positions(status);
CREATE INDEX idx_positions_updated_at ON positions(updated_at);
```
##### cash_accounts (现金账户表)
```sql
CREATE TABLE cash_accounts (
id BIGSERIAL PRIMARY KEY,
account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
balance DECIMAL(18, 2) NOT NULL DEFAULT 0, -- 余额
currency VARCHAR(10) NOT NULL DEFAULT 'CNY',
interest_rate DECIMAL(5, 4) DEFAULT 0, -- 年化利率
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(account_id, currency) -- 同一账户同一货币只能有一条记录
);
CREATE INDEX idx_cash_accounts_account_id ON cash_accounts(account_id);
```
##### position_warnings (持仓预警表)
```sql
CREATE TABLE position_warnings (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
position_id BIGINT NOT NULL REFERENCES positions(id) ON DELETE CASCADE,
warning_type VARCHAR(20) NOT NULL, -- position_limit, price_alert
threshold_value DECIMAL(18, 4), -- 阈值
is_triggered BOOLEAN DEFAULT FALSE,
triggered_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_position_warnings_user_id ON position_warnings(user_id);
CREATE INDEX idx_position_warnings_position_id ON position_warnings(position_id);
```
#### 2.2.3 交易相关表
##### transactions (交易记录表)
```sql
CREATE TABLE transactions (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
position_id BIGINT REFERENCES positions(id) ON DELETE SET NULL, -- 可为空,因为可能是现金交易
trading_plan_id BIGINT REFERENCES trading_plans(id) ON DELETE SET NULL, -- 关联交易计划
type VARCHAR(20) NOT NULL, -- buy, sell, dividend, split, bonus, rights, deposit, withdraw
date DATE NOT NULL, -- 交易日期
symbol VARCHAR(20), -- 股票/基金代码
name VARCHAR(100), -- 股票/基金名称
market VARCHAR(20), -- 市场
shares DECIMAL(18, 4), -- 交易份额
price DECIMAL(18, 4), -- 交易价格
amount DECIMAL(18, 2) NOT NULL, -- 交易金额(正数表示收入,负数表示支出)
fee DECIMAL(18, 2) DEFAULT 0, -- 交易费用
currency VARCHAR(10) NOT NULL DEFAULT 'CNY',
exchange_rate DECIMAL(10, 6) DEFAULT 1, -- 汇率(用于多货币)
metadata JSONB, -- 存储额外信息,如拆股比例、分红金额等
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_transactions_user_id ON transactions(user_id);
CREATE INDEX idx_transactions_account_id ON transactions(account_id);
CREATE INDEX idx_transactions_position_id ON transactions(position_id);
CREATE INDEX idx_transactions_trading_plan_id ON transactions(trading_plan_id);
CREATE INDEX idx_transactions_date ON transactions(date);
CREATE INDEX idx_transactions_type ON transactions(type);
CREATE INDEX idx_transactions_symbol ON transactions(symbol);
CREATE INDEX idx_transactions_date_desc ON transactions(date DESC); -- 用于时间线查询
```
**metadata字段示例**
```json
{
"split_ratio": 2, // 拆股比例
"dividend_per_share": 0.5, // 每股分红
"bonus_ratio": 0.5, // 送股比例
"rights_price": 10.5, // 配股价格
"rights_shares": 100 // 配股数量
}
```
##### transaction_thoughts (交易思考表)
```sql
CREATE TABLE transaction_thoughts (
id BIGSERIAL PRIMARY KEY,
transaction_id BIGINT NOT NULL REFERENCES transactions(id) ON DELETE CASCADE,
content TEXT NOT NULL, -- 思考内容
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(transaction_id) -- 每笔交易只能有一条思考
);
CREATE INDEX idx_transaction_thoughts_transaction_id ON transaction_thoughts(transaction_id);
CREATE INDEX idx_transaction_thoughts_content ON transaction_thoughts USING gin(to_tsvector('jiebacfg', content)); -- 全文搜索索引需要安装pg_trgm扩展
```
##### transaction_reviews (交易复盘表)
```sql
CREATE TABLE transaction_reviews (
id BIGSERIAL PRIMARY KEY,
transaction_id BIGINT NOT NULL REFERENCES transactions(id) ON DELETE CASCADE,
review_type VARCHAR(20) DEFAULT 'manual', -- manual, scheduled
content TEXT NOT NULL, -- 复盘内容
review_date DATE NOT NULL, -- 复盘日期
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_transaction_reviews_transaction_id ON transaction_reviews(transaction_id);
CREATE INDEX idx_transaction_reviews_review_date ON transaction_reviews(review_date);
CREATE INDEX idx_transaction_reviews_content ON transaction_reviews USING gin(to_tsvector('jiebacfg', content)); -- 全文搜索索引
```
**说明:**
- 一笔交易可以有多次复盘(不同时间点)
- `review_type`: manual手动复盘、scheduled定期提醒复盘
#### 2.2.4 计划相关表
##### trading_plans (交易计划表)
```sql
CREATE TABLE trading_plans (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
symbol VARCHAR(20) NOT NULL, -- 股票代码
name VARCHAR(100) NOT NULL, -- 股票名称
market VARCHAR(20) NOT NULL, -- 市场
target_price DECIMAL(18, 4) NOT NULL, -- 目标价格
target_amount DECIMAL(18, 2), -- 目标金额
target_shares DECIMAL(18, 4), -- 目标份额(金额和份额二选一)
deadline DATE, -- 截止日期
status VARCHAR(20) DEFAULT 'pending', -- pending, in_progress, completed, cancelled
progress DECIMAL(5, 2) DEFAULT 0, -- 完成进度(百分比)
current_price DECIMAL(18, 4), -- 当前价格(用于提醒)
is_price_alert_enabled BOOLEAN DEFAULT TRUE, -- 是否启用价格提醒
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP
);
CREATE INDEX idx_trading_plans_user_id ON trading_plans(user_id);
CREATE INDEX idx_trading_plans_status ON trading_plans(status);
CREATE INDEX idx_trading_plans_deadline ON trading_plans(deadline);
CREATE INDEX idx_trading_plans_symbol ON trading_plans(symbol);
```
##### plan_steps (计划步骤表)
```sql
CREATE TABLE plan_steps (
id BIGSERIAL PRIMARY KEY,
trading_plan_id BIGINT NOT NULL REFERENCES trading_plans(id) ON DELETE CASCADE,
step_order INTEGER NOT NULL, -- 步骤顺序1, 2, 3...
target_price DECIMAL(18, 4) NOT NULL, -- 该步骤的目标价格
target_amount DECIMAL(18, 2), -- 该步骤的目标金额
target_shares DECIMAL(18, 4), -- 该步骤的目标份额
status VARCHAR(20) DEFAULT 'pending', -- pending, completed
completed_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(trading_plan_id, step_order)
);
CREATE INDEX idx_plan_steps_trading_plan_id ON plan_steps(trading_plan_id);
CREATE INDEX idx_plan_steps_status ON plan_steps(status);
```
##### plan_thoughts (计划思考表)
```sql
CREATE TABLE plan_thoughts (
id BIGSERIAL PRIMARY KEY,
trading_plan_id BIGINT NOT NULL REFERENCES trading_plans(id) ON DELETE CASCADE,
content TEXT NOT NULL, -- 思考内容
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(trading_plan_id) -- 每个计划只能有一条思考
);
CREATE INDEX idx_plan_thoughts_trading_plan_id ON plan_thoughts(trading_plan_id);
CREATE INDEX idx_plan_thoughts_content ON plan_thoughts USING gin(to_tsvector('jiebacfg', content)); -- 全文搜索索引
```
#### 2.2.5 净值相关表
##### net_value_snapshots (净值快照表)
```sql
CREATE TABLE net_value_snapshots (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
account_id BIGINT REFERENCES accounts(id) ON DELETE CASCADE, -- 可为空,表示总账户
snapshot_date DATE NOT NULL, -- 快照日期
total_asset DECIMAL(18, 2) NOT NULL, -- 总资产
total_cost DECIMAL(18, 2) NOT NULL, -- 总成本(累计投入)
total_profit DECIMAL(18, 2) NOT NULL, -- 总收益
net_value DECIMAL(18, 6) NOT NULL, -- 单位净值
cumulative_return DECIMAL(10, 6) NOT NULL, -- 累计收益率
annualized_return DECIMAL(10, 6), -- 年化收益率
year_to_date_return DECIMAL(10, 6), -- 当年收益率
positions_data JSONB, -- 持仓明细JSON格式便于查询
cash_data JSONB, -- 现金明细
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, account_id, snapshot_date) -- 同一用户同一账户同一日期只能有一条快照
);
CREATE INDEX idx_net_value_snapshots_user_id ON net_value_snapshots(user_id);
CREATE INDEX idx_net_value_snapshots_account_id ON net_value_snapshots(account_id);
CREATE INDEX idx_net_value_snapshots_date ON net_value_snapshots(snapshot_date);
CREATE INDEX idx_net_value_snapshots_user_date ON net_value_snapshots(user_id, snapshot_date DESC);
```
**positions_data字段示例**
```json
[
{
"position_id": 1,
"symbol": "600519",
"name": "贵州茅台",
"shares": 100,
"cost_price": 1600,
"current_price": 1850,
"market_value": 185000,
"profit": 25000
}
]
```
##### daily_net_values (每日净值表)
```sql
CREATE TABLE daily_net_values (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
account_id BIGINT REFERENCES accounts(id) ON DELETE CASCADE, -- 可为空,表示总账户
value_date DATE NOT NULL, -- 净值日期
net_value DECIMAL(18, 6) NOT NULL, -- 单位净值
total_asset DECIMAL(18, 2) NOT NULL, -- 总资产
total_cost DECIMAL(18, 2) NOT NULL, -- 总成本
daily_return DECIMAL(10, 6), -- 日收益率
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, account_id, value_date)
);
CREATE INDEX idx_daily_net_values_user_id ON daily_net_values(user_id);
CREATE INDEX idx_daily_net_values_account_id ON daily_net_values(account_id);
CREATE INDEX idx_daily_net_values_date ON daily_net_values(value_date);
CREATE INDEX idx_daily_net_values_user_date ON daily_net_values(user_id, account_id, value_date DESC); -- 用于净值曲线查询
```
#### 2.2.6 市场数据表
##### market_prices (市场价格表)
```sql
CREATE TABLE market_prices (
id BIGSERIAL PRIMARY KEY,
symbol VARCHAR(20) NOT NULL,
market VARCHAR(20) NOT NULL,
price_date DATE NOT NULL,
open_price DECIMAL(18, 4), -- 开盘价
close_price DECIMAL(18, 4) NOT NULL, -- 收盘价
high_price DECIMAL(18, 4), -- 最高价
low_price DECIMAL(18, 4), -- 最低价
volume BIGINT, -- 成交量
amount DECIMAL(18, 2), -- 成交额
currency VARCHAR(10) DEFAULT 'CNY',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(symbol, market, price_date)
);
CREATE INDEX idx_market_prices_symbol ON market_prices(symbol, market);
CREATE INDEX idx_market_prices_date ON market_prices(price_date);
CREATE INDEX idx_market_prices_symbol_date ON market_prices(symbol, market, price_date DESC);
```
---
## 三、关键业务逻辑实现
### 3.1 基金净值法计算
#### 3.1.1 每日净值计算流程
```sql
-- 1. 计算当日总资产
-- 总资产 = 所有持仓市值 + 现金余额
-- 2. 计算累计投入资金
-- 累计投入 = 所有买入交易的金额总和 - 所有卖出交易的金额总和 + 初始资金
-- 3. 计算单位净值
-- 单位净值 = 总资产 / 累计投入资金
-- 4. 计算收益率
-- 累计收益率 = (当前净值 - 初始净值) / 初始净值
-- 日收益率 = (今日净值 - 昨日净值) / 昨日净值
```
#### 3.1.2 净值计算SQL示例
```sql
-- 计算用户某账户的当日净值
WITH asset_value AS (
-- 计算持仓市值
SELECT
COALESCE(SUM(p.shares * p.current_price), 0) as position_value
FROM positions p
WHERE p.account_id = :account_id
AND p.status = 'active'
),
cash_value AS (
-- 计算现金余额
SELECT
COALESCE(SUM(c.balance), 0) as cash_balance
FROM cash_accounts c
WHERE c.account_id = :account_id
),
total_cost AS (
-- 计算累计投入
SELECT
COALESCE(SUM(
CASE
WHEN t.type IN ('buy', 'deposit') THEN t.amount
WHEN t.type IN ('sell', 'withdraw') THEN -t.amount
ELSE 0
END
), 0) as total_invested
FROM transactions t
WHERE t.account_id = :account_id
)
SELECT
(av.position_value + cv.cash_balance) as total_asset,
tc.total_invested,
CASE
WHEN tc.total_invested > 0
THEN (av.position_value + cv.cash_balance) / tc.total_invested
ELSE 1.0
END as net_value
FROM asset_value av, cash_value cv, total_cost tc;
```
### 3.2 交易记录与思考关联查询
```sql
-- 查询交易记录及其思考(时间线)
SELECT
t.id,
t.date,
t.type,
t.symbol,
t.name,
t.shares,
t.price,
t.amount,
tt.content as thought_content,
tt.created_at as thought_created_at
FROM transactions t
LEFT JOIN transaction_thoughts tt ON t.id = tt.transaction_id
WHERE t.user_id = :user_id
ORDER BY t.date DESC, t.created_at DESC
LIMIT :limit OFFSET :offset;
```
### 3.3 交易复盘查询
```sql
-- 查询交易及其所有复盘记录
SELECT
t.id,
t.date,
t.symbol,
t.name,
t.type,
tr.id as review_id,
tr.review_date,
tr.content as review_content,
tr.created_at as review_created_at
FROM transactions t
LEFT JOIN transaction_reviews tr ON t.id = tr.transaction_id
WHERE t.user_id = :user_id
AND t.id = :transaction_id
ORDER BY tr.review_date DESC;
```
### 3.4 交易计划进度更新
```sql
-- 更新计划进度
UPDATE trading_plans tp
SET
progress = (
SELECT
CASE
WHEN tp.target_amount > 0
THEN LEAST(100, (COALESCE(SUM(CASE WHEN t.type = 'buy' THEN t.amount ELSE 0 END), 0) / tp.target_amount) * 100)
WHEN tp.target_shares > 0
THEN LEAST(100, (COALESCE(SUM(CASE WHEN t.type = 'buy' THEN t.shares ELSE 0 END), 0) / tp.target_shares) * 100)
ELSE 0
END
FROM transactions t
WHERE t.trading_plan_id = tp.id
),
status = CASE
WHEN progress >= 100 THEN 'completed'
WHEN progress > 0 THEN 'in_progress'
ELSE 'pending'
END,
updated_at = CURRENT_TIMESTAMP
WHERE tp.id = :plan_id;
```
---
## 四、索引优化建议
### 4.1 必须创建的索引
1. **外键索引**:所有外键字段都应创建索引
2. **查询字段索引**经常用于WHERE、JOIN、ORDER BY的字段
3. **时间字段索引**date、created_at等时间字段
### 4.2 全文搜索索引
对于思考内容、复盘内容等文本字段使用PostgreSQL的全文搜索功能
```sql
-- 安装扩展(需要管理员权限)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS jieba; -- 中文分词(如果可用)
-- 创建全文搜索索引
CREATE INDEX idx_transaction_thoughts_content_search
ON transaction_thoughts USING gin(to_tsvector('jiebacfg', content));
```
### 4.3 复合索引
根据查询模式创建复合索引:
```sql
-- 用户时间线查询(最常用)
CREATE INDEX idx_transactions_user_date ON transactions(user_id, date DESC, created_at DESC);
-- 净值查询
CREATE INDEX idx_daily_net_values_user_account_date ON daily_net_values(user_id, account_id, value_date DESC);
-- 持仓查询
CREATE INDEX idx_positions_account_status ON positions(account_id, status);
```
---
## 五、数据维护建议
### 5.1 定期任务
1. **每日净值计算**
- 每日收盘后如18:00自动计算并保存净值
- 更新持仓的当前价格
2. **价格数据更新**
- 每日更新市场价格表
- 清理过期的价格数据保留最近2年
3. **计划提醒**
- 检查到达目标价格的计划
- 发送提醒通知
4. **复盘提醒**
- 定期(如每月)提醒用户进行复盘
### 5.2 数据清理
```sql
-- 清理过期的市场价格数据保留最近2年
DELETE FROM market_prices
WHERE price_date < CURRENT_DATE - INTERVAL '2 years';
-- 归档已删除的数据(软删除的数据)
-- 可以考虑定期归档到历史表
```
### 5.3 数据备份
1. **定期全量备份**:每日备份
2. **增量备份**:每小时备份事务日志
3. **重要数据备份**:交易记录、思考内容等关键数据单独备份
---
## 六、性能优化建议
### 6.1 查询优化
1. **分页查询**所有列表查询都应使用LIMIT和OFFSET
2. **避免N+1查询**使用JOIN一次性获取关联数据
3. **使用EXPLAIN分析**:定期分析慢查询
### 6.2 缓存策略
1. **净值数据缓存**最近30天的净值数据可以缓存
2. **持仓数据缓存**:当前持仓数据可以缓存,更新时失效
3. **市场价格缓存**使用Redis缓存常用股票的最新价格
### 6.3 读写分离
如果数据量大,可以考虑:
- 主库:写操作
- 从库:读操作(查询、统计)
---
## 七、数据迁移方案
### 7.1 初始化数据
```sql
-- 创建初始用户
INSERT INTO users (username, password_hash, nickname)
VALUES ('admin', 'hashed_password', '管理员');
-- 创建初始账户
INSERT INTO accounts (user_id, name, type, currency)
VALUES (1, '主账户', 'mixed', 'CNY');
-- 设置初始净值
INSERT INTO daily_net_values (user_id, account_id, value_date, net_value, total_asset, total_cost)
VALUES (1, 1, CURRENT_DATE, 1.0, 0, 0);
```
### 7.2 数据导入
如果从其他系统迁移数据,需要:
1. 清洗数据格式
2. 按依赖关系顺序导入(先用户,再账户,再交易)
3. 重新计算净值历史
---
## 八、安全考虑
### 8.1 数据安全
1. **敏感数据加密**密码使用bcrypt等加密
2. **SQL注入防护**:使用参数化查询
3. **权限控制**:用户只能访问自己的数据
### 8.2 数据完整性
1. **外键约束**:确保数据关联正确
2. **唯一约束**:防止重复数据
3. **检查约束**:确保数据有效性
```sql
-- 示例:检查约束
ALTER TABLE transactions
ADD CONSTRAINT check_transaction_amount
CHECK (amount != 0);
ALTER TABLE positions
ADD CONSTRAINT check_shares_positive
CHECK (shares >= 0);
```
---
## 九、总结
### 9.1 数据库选型
**推荐使用 PostgreSQL 14+**
优势:
- ✅ 关系型数据库,结构清晰
- ✅ JSONB支持灵活存储思考内容
- ✅ 强大的查询能力
- ✅ 全文搜索支持
- ✅ 时间序列查询性能好
### 9.2 核心表设计
1. **用户体系**users, user_settings
2. **账户体系**accounts, positions, cash_accounts
3. **交易体系**transactions, transaction_thoughts, transaction_reviews
4. **计划体系**trading_plans, plan_steps, plan_thoughts
5. **净值体系**daily_net_values, net_value_snapshots
6. **市场数据**market_prices
### 9.3 关键特性
1. **基金净值法**通过daily_net_values表记录每日净值
2. **思考记录**transaction_thoughts和plan_thoughts表
3. **复盘功能**transaction_reviews表支持多次复盘
4. **时间线查询**:通过索引优化时间线查询性能
### 9.4 后续优化
1. 根据实际使用情况调整索引
2. 监控慢查询,持续优化
3. 考虑使用TimescaleDB处理时间序列数据
4. 根据数据量考虑分表策略
---
**文档版本**v1.0
**创建日期**2024年
**数据库版本**PostgreSQL 14+