184 lines
4.6 KiB
Markdown
184 lines
4.6 KiB
Markdown
|
|
---
|
||
|
|
tags: [后端, 数据库, SQL, 基础]
|
||
|
|
create time: 2026-04-24 18:41
|
||
|
|
---
|
||
|
|
|
||
|
|
# 数据库基础
|
||
|
|
|
||
|
|
## 概述
|
||
|
|
|
||
|
|
数据库是后端的核心。几乎每个应用都需要持久化数据——用户信息、订单记录、文章内容……关系型数据库(MySQL/PostgreSQL)是后端开发的基石。
|
||
|
|
|
||
|
|
思考题:为什么关系型数据库能统治这么多年?JSON 数据库(如 MongoDB)出现了,为什么我们仍然需要 SQL?
|
||
|
|
|
||
|
|
## 正文
|
||
|
|
|
||
|
|
### 1. 关系模型
|
||
|
|
|
||
|
|
关系数据库用"表"来组织数据,表与表之间通过"关系"(外键)连接。
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
erDiagram
|
||
|
|
USERS ||--o{ POSTS : creates
|
||
|
|
USERS {
|
||
|
|
int id PK
|
||
|
|
string username
|
||
|
|
string email
|
||
|
|
datetime created_at
|
||
|
|
}
|
||
|
|
POSTS {
|
||
|
|
int id PK
|
||
|
|
int user_id FK
|
||
|
|
string title
|
||
|
|
string content
|
||
|
|
datetime published_at
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 基础 SQL
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 创建表
|
||
|
|
CREATE TABLE users (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||
|
|
email VARCHAR(100) NOT NULL UNIQUE,
|
||
|
|
password VARCHAR(255) NOT NULL,
|
||
|
|
created_at TIMESTAMP DEFAULT NOW()
|
||
|
|
);
|
||
|
|
|
||
|
|
-- 插入数据
|
||
|
|
INSERT INTO users (username, email, password)
|
||
|
|
VALUES ('alice', 'alice@example.com', 'hashed_password');
|
||
|
|
|
||
|
|
-- 查询数据
|
||
|
|
SELECT id, username, email FROM users WHERE id = 1;
|
||
|
|
|
||
|
|
-- 更新数据
|
||
|
|
UPDATE users SET email = 'new@example.com' WHERE id = 1;
|
||
|
|
|
||
|
|
-- 删除数据
|
||
|
|
DELETE FROM users WHERE id = 1;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 多表查询
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- JOIN 联表查询
|
||
|
|
SELECT u.username, p.title, p.content
|
||
|
|
FROM users u
|
||
|
|
INNER JOIN posts p ON u.id = p.user_id
|
||
|
|
WHERE u.id = 1;
|
||
|
|
|
||
|
|
-- 子查询
|
||
|
|
SELECT * FROM users
|
||
|
|
WHERE id IN (SELECT user_id FROM posts WHERE created_at > '2026-01-01');
|
||
|
|
|
||
|
|
-- 聚合查询
|
||
|
|
SELECT user_id, COUNT(*) as post_count
|
||
|
|
FROM posts
|
||
|
|
GROUP BY user_id
|
||
|
|
HAVING post_count > 10;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. 索引 — 数据库性能的命脉
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 创建索引
|
||
|
|
CREATE INDEX idx_posts_user_id ON posts(user_id);
|
||
|
|
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
|
||
|
|
|
||
|
|
-- 复合索引
|
||
|
|
CREATE INDEX idx_user_email ON users(email, created_at);
|
||
|
|
```
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
graph LR
|
||
|
|
A[全表扫描 O(n)] -.->|慢| B[数据量增大]
|
||
|
|
C[索引查找 O(log n)] -.->|快| D[数据量增大]
|
||
|
|
```
|
||
|
|
|
||
|
|
> **核心要点:** 索引加速查询但减慢写入。每条写入(INSERT/UPDATE/DELETE)都需要更新索引。不是所有字段都需要索引,通常只给经常用于 WHERE 和 JOIN 的字段加索引。
|
||
|
|
|
||
|
|
> **提问:** B+ 树索引为什么比哈希索引更适合范围查询?
|
||
|
|
|
||
|
|
### 5. 事务(Transaction)— 数据的最后一道防线
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 事务保证要么全部成功,要么全部回滚
|
||
|
|
BEGIN;
|
||
|
|
|
||
|
|
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
|
||
|
|
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
|
||
|
|
|
||
|
|
-- 检查是否有错误
|
||
|
|
-- COMMIT; → 全部提交
|
||
|
|
-- ROLLBACK; → 全部回滚
|
||
|
|
```
|
||
|
|
|
||
|
|
ACID 四个特性:
|
||
|
|
|
||
|
|
| 特性 | 说明 | 示例 |
|
||
|
|
|------|------|------|
|
||
|
|
| **A**tomivity(原子性) | 事务要么全做,要么全不做 | 转账时扣款和加款必须同时成功 |
|
||
|
|
| **C**onsistency(一致性) | 事务前后数据保持一致 | 转账前后总金额不变 |
|
||
|
|
| **I**solation(隔离性) | 并发事务互不干扰 | 两个用户同时转账不会出问题 |
|
||
|
|
| **D**urability(持久性) | 提交后数据永久保存 | 断电后数据不会丢失 |
|
||
|
|
|
||
|
|
> **思考:** 如果转账过程中服务器在两条 UPDATE 之间崩溃,会发生什么?ACID 的原子性如何避免这个问题?
|
||
|
|
|
||
|
|
### 6. Go 操作数据库示例
|
||
|
|
|
||
|
|
```go
|
||
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"database/sql"
|
||
|
|
"fmt"
|
||
|
|
_ "github.com/lib/pq" // PostgreSQL 驱动
|
||
|
|
)
|
||
|
|
|
||
|
|
type User struct {
|
||
|
|
ID int
|
||
|
|
Username string
|
||
|
|
Email string
|
||
|
|
}
|
||
|
|
|
||
|
|
func getUser(db *sql.DB, id int) (*User, error) {
|
||
|
|
var user User
|
||
|
|
err := db.QueryRow("SELECT id, username, email FROM users WHERE id = $1", id).
|
||
|
|
Scan(&user.ID, &user.Username, &user.Email)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &user, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// 事务示例
|
||
|
|
func transfer(db *sql.DB, fromID, toID, amount int) error {
|
||
|
|
tx, err := db.Begin()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer tx.Rollback() // 出错时自动回滚
|
||
|
|
|
||
|
|
_, err = tx.Exec("UPDATE accounts SET balance = balance - $1 WHERE user_id = $2", amount, fromID)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err = tx.Exec("UPDATE accounts SET balance = balance + $1 WHERE user_id = $2", amount, toID)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
return tx.Commit()
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 关联笔记
|
||
|
|
|
||
|
|
- [[API 设计]] — API 需要操作数据库
|
||
|
|
- [[Go 后端基础]] — Go 连接数据库的实践
|
||
|
|
- [[30.areas/finance/Investment lessons/2024.Current trading lessons.md]]
|