MongoDB入门学习教程,从入门到精通,MongoDB 持久性完全指南(20)

MongoDB 持久性完全指南

第一部分:持久性概念与日志机制

1.1 持久性概述

持久性是数据库系统的一种属性,它保证了提交给数据库的写操作将永久保存在数据库中。如果票务系统通知你座位已预订成功,那么即使系统崩溃,你的预订记录也应该存在。

MongoDB 通过多层机制保证持久性:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    持久性保证层次                            │
├─────────────────────────────────────────────────────────────┤
│  事务持久性 ← 写关注 + 读关注组合                              │
│       ↑                                                      │
│  集群级别持久性 ← 写关注(majority) + 读关注(majority)         │
│       ↑                                                      │
│  成员级别持久性 ← Journal 日志机制                           │
│       ↑                                                      │
│  物理存储 ← 磁盘硬件 + 文件系统                               │
└─────────────────────────────────────────────────────────────┘

1.2 Journal 日志机制详解

Journal 是一种预写式日志机制,其核心原理是:在对数据库做更改之前,先将更改操作写入持久介质。

javascript 复制代码
// ============================================
// Journal 工作原理示意图
// ============================================
/*
写操作流程:
1. 应用程序发起写操作
2. 操作先写入 Journal 日志(内存)
3. 定期刷新 Journal 到磁盘(默认每100ms)
4. 数据应用到数据文件(默认每60s)
5. 确认写操作完成

崩溃恢复流程:
1. 服务器重启
2. 检测到未正常关闭
3. 读取 Journal 文件
4. 重放未持久化的写操作
5. 完成数据恢复
*/
1.2.1 配置 Journal
yaml 复制代码
# /etc/mongod.conf
# Journal 配置示例

storage:
  # 数据存储路径
  dbPath: /var/lib/mongodb
  
  # Journal 配置
  journal:
    # 启用 Journal(生产环境强烈推荐)
    enabled: true
    
    # 提交间隔(毫秒),默认100ms
    # 范围:2ms - 500ms
    commitIntervalMs: 100

# 启动时也可通过命令行指定
# mongod --dbpath /data/db --journal
# mongod --dbpath /data/db --nojournal  # 禁用(不推荐生产环境)
1.2.2 查看 Journal 状态
javascript 复制代码
// ============================================
// 检查 Journal 状态
// ============================================

// 1. 获取服务器状态,查看 Journal 信息
db.serverStatus().storageEngine
// 输出示例:
// {
//   "name" : "wiredTiger",
//   "supportsCommittedReads" : true,
//   "persistent" : true,        // 是否持久化
//   "journal" : {               // Journal 信息
//     "enabled" : true          // Journal 已启用
//   }
// }

// 2. 查看 Journal 统计信息
db.serverStatus().wiredTiger.log
// 输出示例:
// {
//   "log size (bytes)" : 1234567,
//   "log writes" : 12345,
//   "log syncs" : 1234,
//   "total log buffer size" : 123456
// }

// 3. 检查数据目录中的 Journal 文件
// 在系统命令行执行:
// ls -la /var/lib/mongodb/journal/
// 文件命名格式:j._<sequence_number>
1.2.3 强制 Journal 刷盘
javascript 复制代码
// ============================================
// 强制写操作立即刷入 Journal
// ============================================

// 方法1:在写操作中使用 j: true
db.orders.insertOne(
  { orderId: "ORD-001", amount: 100, status: "pending" },
  { writeConcern: { w: 1, j: true } }
)
// j: true 表示必须等待 Journal 写入磁盘后才返回确认

// 方法2:批量操作中使用
db.orders.insertMany(
  [
    { orderId: "ORD-002", amount: 200, status: "pending" },
    { orderId: "ORD-003", amount: 300, status: "pending" }
  ],
  { writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)

// 方法3:修改 Journal 提交间隔(运行时)
db.adminCommand({ setParameter: 1, journalCommitInterval: 30 })
// 将提交间隔设置为30ms,更频繁的刷盘提供更好的持久性保证
1.2.4 Journal 性能考虑
javascript 复制代码
// ============================================
// Journal 性能优化示例
// ============================================

// 场景1:金融交易 - 需要最高持久性保证
async function financialTransaction() {
  const session = client.startSession();
  try {
    session.startTransaction();
    
    // 关键操作使用强持久性
    await db.accounts.updateOne(
      { accountId: "ACC-001" },
      { $inc: { balance: -100 } },
      { 
        session,
        writeConcern: { w: "majority", j: true }  // 必须 Journal 刷盘
      }
    );
    
    await db.accounts.updateOne(
      { accountId: "ACC-002" },
      { $inc: { balance: 100 } },
      { 
        session,
        writeConcern: { w: "majority", j: true }
      }
    );
    
    await session.commitTransaction();
    console.log("交易成功,已持久化到磁盘");
  } catch (err) {
    await session.abortTransaction();
    console.error("交易失败:", err);
  } finally {
    session.endSession();
  }
}

// 场景2:日志数据 - 可以接受较低持久性
async function writeLogEntry(logData) {
  // 日志数据可以允许少量丢失,换取更高性能
  await db.logs.insertOne(
    { 
      timestamp: new Date(),
      level: "INFO",
      message: logData
    },
    { 
      writeConcern: { w: 0, j: false }  // 不等待确认,不 Journal
    }
  );
}
// w:0 表示不等待任何确认,数据可能丢失但写入速度最快

第二部分:写关注 - 集群级别持久性

2.1 写关注参数详解

写关注定义了 MongoDB 对写操作的确认级别,语法格式为:

javascript 复制代码
{ w: <value>, j: <boolean>, wtimeout: <number> }
参数 说明 可选值
w 需要确认的节点数 0, 1, n, "majority", "all", tag
j 是否等待 Journal 刷盘 true, false
wtimeout 超时时间(毫秒) 正整数
javascript 复制代码
// ============================================
// 写关注级别详解与示例
// ============================================

// 1. w: 0 - 不等待确认(最高性能,最低持久性)
// 适用场景:非关键日志、监控数据
db.events.insertOne(
  { event: "page_view", userId: 123, timestamp: new Date() },
  { writeConcern: { w: 0 } }
)
// 注意:MongoDB 5.0+ 隐式默认 w: "majority"

// 2. w: 1 - 仅主节点确认(默认行为)
// 适用场景:可容忍短暂丢失的普通业务数据
db.products.updateOne(
  { sku: "ABC-123" },
  { $set: { price: 29.99 } },
  { writeConcern: { w: 1 } }
)

// 3. w: 2 - 等待主节点 + 1个从节点确认
// 适用场景:需要一定程度冗余的数据
db.orders.insertOne(
  { orderId: "ORD-100", total: 299.99, customerId: "CUST-001" },
  { writeConcern: { w: 2, wtimeout: 5000 } }  // 5秒超时
)

// 4. w: "majority" - 大多数节点确认(推荐生产环境)
// 适用场景:关键业务数据
// 3节点副本集 majority = 2
// 5节点副本集 majority = 3
db.payments.insertOne(
  { 
    paymentId: "PAY-001", 
    amount: 1500.00, 
    status: "completed",
    timestamp: new Date()
  },
  { 
    writeConcern: { 
      w: "majority",   // 大多数节点确认
      j: true,         // Journal 已刷盘
      wtimeout: 10000  // 10秒超时
    } 
  }
)

// 5. w: "all" - 所有节点确认(最高持久性,最低性能)
// 适用场景:极其关键的数据,如法规要求
db.legalDocuments.insertOne(
  { docId: "LEGAL-001", content: "...", signature: "..." },
  { writeConcern: { w: "all", j: true, wtimeout: 30000 } }
)

// 6. 使用自定义标签
// 先配置节点标签(在 mongod.conf 中):
/*
replication:
  replSetName: rs0
  tags:
    dc: "us-east"
    use: "reporting"
*/
// 然后使用标签指定写关注
db.reports.insertOne(
  { reportId: "RPT-001", data: "..." },
  { writeConcern: { w: "dc", wtimeout: 5000 } }  // 等待特定标签的节点
)

2.2 wtimeout 超时处理

javascript 复制代码
// ============================================
// wtimeout 超时示例
// ============================================

// 场景:设置超时防止无限阻塞
async function insertWithTimeout() {
  try {
    const result = await db.orders.insertOne(
      { orderId: "ORD-TIMEOUT", items: [], total: 0 },
      { 
        writeConcern: { 
          w: "majority", 
          j: true, 
          wtimeout: 3000    // 3秒超时
        } 
      }
    );
    console.log("写入成功:", result);
  } catch (error) {
    // 超时时会抛出 WriteConcernError
    console.error("写关注超时,但数据可能仍在传播:", error);
    // 注意:超时不代表写入失败,只是确认延迟
    // 数据最终可能会被应用到大多数节点
  }
}

2.3 不同级别配置写关注

javascript 复制代码
// ============================================
// 在不同级别配置写关注
// ============================================

const { MongoClient } = require('mongodb');

// 1. 客户端级别配置(所有操作默认使用)
const client = new MongoClient("mongodb://localhost:27017", {
  writeConcern: { w: "majority", j: true, wtimeout: 5000 }
});

// 2. 数据库级别配置
const db = client.db("ecommerce", {
  writeConcern: { w: "majority", j: true }
});

// 3. 集合级别配置
const collection = db.collection("orders", {
  writeConcern: { w: 2, wtimeout: 3000 }
});

// 4. 操作级别配置(优先级最高)
await collection.insertOne(
  { item: "laptop", qty: 1, price: 999.99 },
  { writeConcern: { w: "all", j: true } }  // 覆盖更高级别的配置
);

// 5. URI 连接字符串配置
const uri = "mongodb://localhost:27017/?w=majority&journal=true&wtimeoutMS=5000";
const clientWithUri = new MongoClient(uri);

第三部分:读关注 - 一致性保证

3.1 读关注级别

读关注指定了读取操作的数据一致性保证:

javascript 复制代码
// ============================================
// 读关注级别详解
// ============================================

// 1. "local" - 默认级别,返回节点本地最新数据
// 特点:最快,但数据可能被回滚
db.orders.find({ status: "pending" }).readConcern("local")

// 2. "available" - 类似 local,用于分片集群
// 特点:不考虑分片间的数据一致性
db.orders.find({}).readConcern("available")

// 3. "majority" - 返回已被大多数节点确认的数据
// 特点:数据不会回滚,保证持久性
db.orders.find({ customerId: "CUST-001" }).readConcern("majority")

// 4. "linearizable" - 最强一致性
// 特点:保证读取到最新已提交的数据,但性能开销大
db.accounts.findOne(
  { accountId: "ACC-001" },
  { readConcern: { level: "linearizable" } }
)

// 5. "snapshot" - 快照隔离(用于事务)
const session = client.startSession();
session.startTransaction({
  readConcern: { level: "snapshot" },
  writeConcern: { w: "majority" }
});

3.2 读关注完整示例

javascript 复制代码
// ============================================
// 读关注实践示例
// ============================================

const { MongoClient } = require('mongodb');

async function readConcernExamples() {
  const client = new MongoClient("mongodb://localhost:27017");
  await client.connect();
  
  const db = client.db("ecommerce");
  const orders = db.collection("orders");
  
  // 1. 默认 local 读关注 - 最快但可能读到未提交数据
  const localResult = await orders.find(
    { status: "shipped" },
    { readConcern: { level: "local" } }
  ).toArray();
  console.log("Local 读关注结果:", localResult.length);
  
  // 2. majority 读关注 - 只读已提交且持久化的数据
  const majorityResult = await orders.find(
    { status: "shipped" },
    { readConcern: { level: "majority" } }
  ).toArray();
  console.log("Majority 读关注结果:", majorityResult.length);
  
  // 3. 在聚合管道中使用读关注
  const pipeline = [
    { $match: { orderDate: { $gte: new Date("2024-01-01") } } },
    { $group: { _id: "$customerId", total: { $sum: "$amount" } } }
  ];
  
  const aggregationResult = await orders.aggregate(pipeline, {
    readConcern: { level: "majority" }
  }).toArray();
  
  // 4. 不同级别配置读关注
  // 数据库级别
  const dbWithReadConcern = client.db("ecommerce", {
    readConcern: { level: "majority" }
  });
  
  // 集合级别
  const collectionWithReadConcern = db.collection("orders", {
    readConcern: { level: "linearizable" }
  });
}

// 5. 因果一致性示例
// 需要 majority 读关注 + majority 写关注
async function causalConsistency() {
  const client = new MongoClient("mongodb://localhost:27017");
  await client.connect();
  
  // 开启因果一致的会话
  const session = client.startSession({ causalConsistency: true });
  
  try {
    // 写入操作使用 majority
    await session.getDatabase("ecommerce").collection("orders").insertOne(
      { orderId: "ORD-CAUSAL", amount: 500, status: "new" },
      { session, writeConcern: { w: "majority", j: true } }
    );
    
    // 读取操作使用 majority - 保证能读到刚写入的数据
    const result = await session.getDatabase("ecommerce").collection("orders").findOne(
      { orderId: "ORD-CAUSAL" },
      { session, readConcern: { level: "majority" } }
    );
    
    console.log("因果一致性保证 - 读到刚写入的数据:", result);
  } finally {
    await session.endSession();
  }
}

第四部分:事务持久性

4.1 事务中的持久性配置

javascript 复制代码
// ============================================
// 事务持久性完整示例
// ============================================

const { MongoClient } = require('mongodb');

async function transactionPersistence() {
  const client = new MongoClient("mongodb://localhost:27017");
  await client.connect();
  
  const session = client.startSession();
  
  try {
    // 事务级别配置读写关注
    const transactionOptions = {
      readConcern: { level: "snapshot" },      // 快照隔离
      writeConcern: { w: "majority", j: true, wtimeout: 10000 },
      readPreference: "primary"                 // 事务必须在主节点执行
    };
    
    session.startTransaction(transactionOptions);
    
    const accounts = session.getDatabase("banking").collection("accounts");
    const transactions = session.getDatabase("banking").collection("transactions");
    
    // 操作1:扣减源账户
    const debitResult = await accounts.updateOne(
      { accountId: "A001", balance: { $gte: 1000 } },
      { $inc: { balance: -1000 } },
      { session }
    );
    
    if (debitResult.modifiedCount !== 1) {
      throw new Error("余额不足");
    }
    
    // 操作2:增加目标账户
    await accounts.updateOne(
      { accountId: "A002" },
      { $inc: { balance: 1000 } },
      { session }
    );
    
    // 操作3:记录交易日志(使用 j: true 确保持久化)
    await transactions.insertOne(
      {
        from: "A001",
        to: "A002",
        amount: 1000,
        timestamp: new Date(),
        status: "completed"
      },
      { session, writeConcern: { w: "majority", j: true } }
    );
    
    // 提交事务 - 所有变更原子性持久化
    await session.commitTransaction();
    console.log("事务提交成功,所有变更已持久化");
    
  } catch (error) {
    // 事务回滚 - 不会有任何变更持久化
    await session.abortTransaction();
    console.error("事务失败已回滚:", error);
  } finally {
    await session.endSession();
  }
}

4.2 事务与写关注组合效果

javascript 复制代码
// ============================================
// 不同读写关注组合的效果
// ============================================

/*
根据官方文档,不同组合提供不同的保证:

| 读关注 | 写关注 | 读己之写 | 单调读 | 单调写 | 写后读 |
|--------|--------|----------|--------|--------|--------|
| majority | majority | ✅ | ✅ | ✅ | ✅ |
| majority | w:1 | ✅ | ✅ | ❌ | ✅ |
| local | w:1 | ❌ | ❌ | ❌ | ❌ |
*/

async function demonstrateCombinations() {
  const client = new MongoClient("mongodb://localhost:27017");
  await client.connect();
  
  // 组合1:最强一致性(majority + majority)
  const session1 = client.startSession({ causalConsistency: true });
  session1.startTransaction({
    readConcern: { level: "majority" },
    writeConcern: { w: "majority", j: true }
  });
  // 提供完整的因果一致性保证
  
  // 组合2:读己之写保证,但可能丢失单调写保证
  const session2 = client.startSession({ causalConsistency: true });
  session2.startTransaction({
    readConcern: { level: "majority" },
    writeConcern: { w: 1 }
  });
  // 能读到自己的写入,但不保证写入顺序的单调性
  
  // 组合3:无因果一致性保证
  const session3 = client.startSession({ causalConsistency: true });
  session3.startTransaction({
    readConcern: { level: "local" },
    writeConcern: { w: 1 }
  });
  // 不提供任何因果一致性保证
}

第五部分:MongoDB 不能保证什么

5.1 硬件和文件系统限制

javascript 复制代码
// ============================================
// MongoDB 持久性限制说明
// ============================================

/*
MongoDB 无法保护免受以下情况影响:

1. 硬件故障
   - 磁盘损坏或坏道
   - 内存错误
   - 电源故障导致的数据损坏

2. 文件系统问题
   - 文件系统损坏
   - 磁盘空间不足导致的不完整写入
   - 文件系统缓存丢失

3. 人为错误
   - 误删除数据
   - 错误的数据更新
   - 配置错误
*/

// 缓解措施:使用副本集 + 定期备份
async function mitigationStrategies() {
  // 1. 配置副本集提供冗余
  // rs.conf() 确认副本集配置
  
  // 2. 定期备份
  // mongodump --host localhost --port 27017 --out /backup/$(date +%Y%m%d)
  
  // 3. 启用审计日志
  // auditLog:
  //   destination: file
  //   format: JSON
  //   path: /var/log/mongodb/audit.json
  
  // 4. 使用写关注 majority 确保数据在多节点存在
  await db.collection("critical_data").insertOne(
    { data: "important" },
    { writeConcern: { w: "majority", j: true } }
  );
}

5.2 回滚场景

javascript 复制代码
// ============================================
// 可能导致数据回滚的场景
// ============================================

/*
数据回滚发生在副本集故障转移期间:
- 原主节点在网络分区期间接受了写入
- 新主节点被选举出来
- 原主节点重新加入时,其独有写入会被回滚
*/

async function rollbackScenario() {
  // 为防止回滚导致的数据丢失,使用 majority 写关注
  await db.collection("important_data").insertOne(
    { transactionId: "TX-001", amount: 1000, status: "committed" },
    { 
      writeConcern: { 
        w: "majority",    // 等待大多数节点确认
        j: true,          // Journal 已持久化
        wtimeout: 5000
      } 
    }
  );
  // 被 majority 确认的写操作不会在故障转移时回滚
}

// 查看回滚文件
// 回滚的数据保存在 rollback 目录中
// ls /var/lib/mongodb/rollback/

第六部分:检查数据损坏与修复

6.1 验证数据完整性

javascript 复制代码
// ============================================
// 使用 validate 命令检查集合完整性
// ============================================

// 1. 验证单个集合
db.orders.validate({ full: true })
// 输出示例:
// {
//   "ns" : "ecommerce.orders",
//   "nrecords" : 12500,
//   "nIndexes" : 3,
//   "valid" : true,        // true 表示无损坏
//   "errors" : [ ],        // 如有错误会列出
//   "warnings" : [ ],
//   "ok" : 1
// }

// 2. 验证所有集合
db.getCollectionInfos().forEach(coll => {
  const result = db.getCollection(coll.name).validate({ full: false });
  print(`${coll.name}: ${result.valid ? 'OK' : 'CORRUPTED'}`);
});

// 3. 检查特定索引
db.orders.validate({ full: true, scandata: true })

// 4. 检测到损坏时的输出示例
// {
//   "ns" : "ecommerce.orders",
//   "valid" : false,
//   "errors" : [
//     "Invalid BSONObj size: 285213831 (0x87040011)",
//     "first element: _id: ObjectId('...')"
//   ],
//   "ok" : 1
// }

6.2 数据修复方法

javascript 复制代码
// ============================================
// 数据修复操作
// ============================================

// 方法1:repairDatabase() 修复当前数据库
db.repairDatabase()
// 注意:修复过程需要额外磁盘空间(约为数据大小)
// 修复会删除损坏的数据,执行前务必备份

// 方法2:修复特定集合(如发现损坏文档)
// 根据 validate 输出找到损坏文档的 _id
db.orders.remove({ _id: ObjectId("损坏文档的ID") })
// 如果可以定位到具体损坏文档,可以只删除该文档

// 方法3:使用 mongod --repair 命令行修复
// 在系统命令行执行:
// mongod --dbpath /var/lib/mongodb --repair
// 或指定修复路径:
// mongod --dbpath /var/lib/mongodb --repair --repairpath /large_disk/repair

// 方法4:从副本集重新同步
// 1. 停止损坏的节点
// 2. 删除数据目录
// 3. 重新启动,让它从其他节点同步
// rm -rf /var/lib/mongodb/*
// systemctl start mongod

6.3 预防措施

javascript 复制代码
// ============================================
// 数据损坏预防最佳实践
// ============================================

// 1. 始终启用 Journal
// storage:
//   journal:
//     enabled: true

// 2. 使用副本集提供冗余
// replication:
//   replSetName: "rs0"

// 3. 定期运行 validate 检查
// 创建监控脚本
db.adminCommand({
  runCommandOnEachDB: function(db) {
    var collections = db.getCollectionInfos();
    collections.forEach(function(coll) {
      var result = db.getCollection(coll.name).validate({ full: false });
      if (!result.valid) {
        print("损坏检测: " + db.getName() + "." + coll.name);
      }
    });
  }
});

// 4. 配置监控告警
// 使用 db.serverStatus() 监控存储引擎状态
const status = db.serverStatus();
if (status.storageEngine.persistent === false) {
  console.warn("警告: 持久化未启用!");
}

// 5. 使用合适的文件系统
// 推荐 XFS 或 ext4,避免使用 ext3

6.4 故障排查命令

bash 复制代码
# ============================================
# 系统级故障排查命令
# ============================================

# 1. 检查 MongoDB 日志
tail -f /var/log/mongodb/mongod.log

# 2. 使用 mongostat 监控状态
mongostat --host localhost --port 27017 -u admin -p --authenticationDatabase admin

# 3. 使用 mongotop 查看集合读写负载
mongotop --host localhost --port 27017 5

# 4. 检查磁盘健康状态
smartctl -a /dev/sda

# 5. 检查文件系统
fsck /dev/sda1

# 6. 查看系统日志中的磁盘错误
dmesg | grep -i "error\|fail\|corrupt"
javascript 复制代码
// ============================================
// MongoDB 内部诊断命令
// ============================================

// 1. 检查当前操作
db.currentOp({ active: true })

// 2. 查看服务器状态
db.serverStatus({
  wiredTiger: 1,
  locks: 1,
  network: 1
})

// 3. 检查副本集状态
rs.status()

// 4. 查看存储引擎统计
db.runCommand({ getParameter: 1, "wiredTigerEngineRuntimeConfig": 1 })

// 5. 检查数据文件完整性
db.runCommand({ 
  validateDB: 1, 
  full: true,
  scandata: true 
})

总结

持久性配置决策矩阵

数据重要性 写关注 读关注 Journal 性能影响
日志/监控 w:0 local 可选 最低
普通业务 w:1 local 建议启用
重要业务 w:"majority" majority 必须启用 中等
金融交易 w:"all", j:true majority 必须启用
法规要求 w:"all", j:true linearizable 必须启用 最高

快速参考命令

bash 复制代码
# 启用 Journal
mongod --journal

# 查看 Journal 状态
db.serverStatus().storageEngine.journal

# 高持久性写入
db.coll.insert(data, {writeConcern:{w:"majority",j:true}})

# 验证数据完整性
db.coll.validate({full:true})

# 修复数据库
db.repairDatabase()

# 查看副本集状态
rs.status()

黄金法则

  1. 生产环境永远启用 Journal
  2. 关键数据使用 w: "majority" + j: true
  3. 定期运行 validate() 检查数据完整性
  4. 保持副本集至少3个节点
  5. 定期备份,定期测试恢复流程
相关推荐
何中应2 小时前
Doris部署&连接
大数据·数据库·时序数据库·doris
老鱼说AI2 小时前
长文预警!大模型面试:关于大模型微调的进阶与工程部署讲解
人工智能·深度学习·神经网络·学习·自然语言处理·面试·职场和发展
云边有个稻草人2 小时前
KES 表空间目录自动创建特性:简化存储管理、提升运维效率
数据库·国产数据库·kes
clear sky .2 小时前
[linux]buildroot什么用途
linux·运维·数据库
羊小蜜.2 小时前
Mysql 12: 视图全解——从创建到使用
android·数据库·mysql·视图
一个有温度的技术博主3 小时前
Redis缓存预热:解决服务冷启动的“数据库杀手”问题
数据库·redis·缓存
沃尔威武10 小时前
数据库 Sinks(.net8)
数据库·.net·webview
北顾笙98011 小时前
LLM学习-day02
学习
Dreamboat¿11 小时前
SQL 注入漏洞
数据库·sql