Skip to content

Sequelize 数据库连接实战 - 连接池配置与最佳实践

发布时间:2024-01-15
作者:一介布衣
标签:Sequelize, 数据库连接, 连接池, 性能优化

前言

上一篇文章我们搭建好了 Sequelize 的开发环境,今天咱们来深入了解数据库连接的各种姿势。说实话,数据库连接这块看似简单,但里面的门道还真不少。

我记得刚开始用 Sequelize 的时候,经常遇到连接超时、连接池耗尽等问题,特别是在高并发场景下,这些问题更加明显。经过一段时间的摸索和踩坑,总结了一些实用的经验,今天分享给大家。

连接方式详解

Sequelize 提供了多种数据库连接方式,我们来逐一了解。

1. 连接字符串方式

这是最简单直接的方式:

javascript
const { Sequelize } = require('sequelize');

// SQLite
const sequelize = new Sequelize('sqlite::memory:');
// 或者文件数据库
const sequelize = new Sequelize('sqlite:./database.sqlite');

// PostgreSQL
const sequelize = new Sequelize('postgres://user:password@localhost:5432/dbname');

// MySQL
const sequelize = new Sequelize('mysql://user:password@localhost:3306/dbname');

优点:配置简单,一行搞定
缺点:不够灵活,难以进行高级配置

2. 参数对象方式

这是我推荐的方式,配置更加灵活:

javascript
const sequelize = new Sequelize({
  dialect: 'postgres',
  host: 'localhost',
  port: 5432,
  database: 'myapp',
  username: 'postgres',
  password: 'password',
  
  // 连接池配置
  pool: {
    max: 10,
    min: 0,
    acquire: 30000,
    idle: 10000
  },
  
  // 其他配置
  logging: false,
  timezone: '+08:00'
});

3. 分离配置方式

在实际项目中,我喜欢把配置单独抽出来:

javascript
// config/database.js
const config = {
  development: {
    dialect: 'sqlite',
    storage: './dev-database.sqlite',
    logging: console.log
  },
  test: {
    dialect: 'sqlite',
    storage: ':memory:',
    logging: false
  },
  production: {
    dialect: 'postgres',
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    database: process.env.DB_NAME,
    username: process.env.DB_USER,
    password: process.env.DB_PASS,
    pool: {
      max: 20,
      min: 5,
      acquire: 60000,
      idle: 10000
    },
    logging: false
  }
};

const env = process.env.NODE_ENV || 'development';
module.exports = config[env];

连接池深度解析

连接池是 Sequelize 性能优化的关键,我们来详细了解一下。

连接池参数详解

javascript
pool: {
  max: 10,        // 最大连接数
  min: 0,         // 最小连接数
  acquire: 30000, // 获取连接的最大等待时间(毫秒)
  idle: 10000,    // 连接空闲多久后释放(毫秒)
  evict: 1000,    // 检查空闲连接的间隔时间
  handleDisconnects: true // 自动处理断开的连接
}

不同场景的连接池配置

小型应用(日活 < 1000)

javascript
pool: {
  max: 5,
  min: 1,
  acquire: 30000,
  idle: 10000
}

中型应用(日活 1000-10000)

javascript
pool: {
  max: 15,
  min: 3,
  acquire: 60000,
  idle: 10000
}

大型应用(日活 > 10000)

javascript
pool: {
  max: 30,
  min: 10,
  acquire: 60000,
  idle: 5000
}

连接池监控

我写了一个简单的连接池监控函数:

javascript
function monitorConnectionPool(sequelize) {
  const pool = sequelize.connectionManager.pool;
  
  setInterval(() => {
    console.log('连接池状态:', {
      总连接数: pool.size,
      可用连接: pool.available,
      使用中连接: pool.using,
      等待连接: pool.waiting
    });
  }, 10000); // 每10秒检查一次
}

// 使用
monitorConnectionPool(sequelize);

连接测试与健康检查

基础连接测试

javascript
async function testConnection() {
  try {
    await sequelize.authenticate();
    console.log('✅ 数据库连接正常');
    return true;
  } catch (error) {
    console.error('❌ 数据库连接失败:', error.message);
    return false;
  }
}

高级健康检查

javascript
async function healthCheck() {
  const startTime = Date.now();
  
  try {
    // 测试连接
    await sequelize.authenticate();
    
    // 测试查询性能
    const [results] = await sequelize.query('SELECT 1 as test');
    
    const responseTime = Date.now() - startTime;
    
    return {
      status: 'healthy',
      responseTime: `\${responseTime}ms`,
      database: sequelize.getDialect(),
      timestamp: new Date().toISOString()
    };
  } catch (error) {
    return {
      status: 'unhealthy',
      error: error.message,
      timestamp: new Date().toISOString()
    };
  }
}

// 定期健康检查
setInterval(async () => {
  const health = await healthCheck();
  console.log('数据库健康状态:', health);
}, 30000);

多数据库连接管理

在一些复杂的项目中,可能需要连接多个数据库:

javascript
// connections/index.js
const { Sequelize } = require('sequelize');

// 主数据库
const mainDB = new Sequelize({
  dialect: 'postgres',
  host: 'main-db-host',
  database: 'main_db',
  username: 'user',
  password: 'pass'
});

// 日志数据库
const logDB = new Sequelize({
  dialect: 'mysql',
  host: 'log-db-host',
  database: 'log_db',
  username: 'user',
  password: 'pass'
});

// 缓存数据库
const cacheDB = new Sequelize({
  dialect: 'sqlite',
  storage: './cache.sqlite'
});

module.exports = {
  mainDB,
  logDB,
  cacheDB
};

使用时:

javascript
const { mainDB, logDB } = require('./connections');

// 在主数据库中查询用户
const user = await mainDB.models.User.findByPk(1);

// 在日志数据库中记录操作
await logDB.models.Log.create({
  action: 'user_query',
  userId: user.id,
  timestamp: new Date()
});

常见连接问题解决

1. 连接超时

现象SequelizeConnectionAcquireTimeoutError

原因:连接池耗尽或数据库响应慢

解决方案

javascript
// 增加连接池大小和超时时间
pool: {
  max: 20,
  acquire: 60000
}

// 或者优化查询,减少长时间占用连接

2. 连接断开

现象SequelizeConnectionError: Connection lost

解决方案

javascript
// 启用自动重连
pool: {
  handleDisconnects: true,
  retry: {
    max: 3
  }
}

// 或者手动重连
sequelize.connectionManager.initPools();

3. SSL 连接问题

现象:SSL 相关错误

解决方案

javascript
// PostgreSQL SSL 配置
const sequelize = new Sequelize({
  dialect: 'postgres',
  dialectOptions: {
    ssl: {
      require: true,
      rejectUnauthorized: false // 开发环境可以设为 false
    }
  }
});

// MySQL SSL 配置
const sequelize = new Sequelize({
  dialect: 'mysql',
  dialectOptions: {
    ssl: {
      ca: fs.readFileSync('./ssl/ca-cert.pem'),
      key: fs.readFileSync('./ssl/client-key.pem'),
      cert: fs.readFileSync('./ssl/client-cert.pem')
    }
  }
});

性能优化技巧

1. 连接复用

javascript
// 不好的做法:每次都创建新连接
function badExample() {
  const sequelize = new Sequelize(/* config */);
  return sequelize.query('SELECT * FROM users');
}

// 好的做法:复用连接
const sequelize = new Sequelize(/* config */);
function goodExample() {
  return sequelize.query('SELECT * FROM users');
}

2. 预热连接池

javascript
async function warmUpPool() {
  const promises = [];
  for (let i = 0; i < 5; i++) {
    promises.push(sequelize.query('SELECT 1'));
  }
  await Promise.all(promises);
  console.log('连接池预热完成');
}

// 应用启动时预热
warmUpPool();

3. 优雅关闭

javascript
async function gracefulShutdown() {
  console.log('正在关闭数据库连接...');
  await sequelize.close();
  console.log('数据库连接已关闭');
  process.exit(0);
}

process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);

实战案例

我来分享一个实际项目中的连接配置:

javascript
// config/database.js
const { Sequelize } = require('sequelize');

class DatabaseManager {
  constructor() {
    this.sequelize = null;
    this.isConnected = false;
  }

  async connect() {
    if (this.isConnected) return this.sequelize;

    this.sequelize = new Sequelize({
      dialect: 'postgres',
      host: process.env.DB_HOST,
      port: process.env.DB_PORT,
      database: process.env.DB_NAME,
      username: process.env.DB_USER,
      password: process.env.DB_PASS,
      
      pool: {
        max: 15,
        min: 3,
        acquire: 60000,
        idle: 10000,
        handleDisconnects: true
      },
      
      logging: process.env.NODE_ENV === 'development' ? console.log : false,
      
      retry: {
        max: 3,
        timeout: 5000
      }
    });

    try {
      await this.sequelize.authenticate();
      this.isConnected = true;
      console.log('✅ 数据库连接成功');
      return this.sequelize;
    } catch (error) {
      console.error('❌ 数据库连接失败:', error);
      throw error;
    }
  }

  async disconnect() {
    if (this.sequelize) {
      await this.sequelize.close();
      this.isConnected = false;
      console.log('数据库连接已关闭');
    }
  }
}

module.exports = new DatabaseManager();

总结

今天我们深入学习了 Sequelize 的数据库连接,主要内容包括:

  • ✅ 多种连接方式的使用场景
  • ✅ 连接池的配置和优化
  • ✅ 连接监控和健康检查
  • ✅ 多数据库连接管理
  • ✅ 常见问题的解决方案
  • ✅ 性能优化技巧

掌握了这些知识,你就能够:

  • 根据项目规模合理配置连接池
  • 及时发现和解决连接问题
  • 优化数据库连接性能
  • 处理复杂的多数据库场景

下一篇文章,我们将开始学习 Sequelize 的核心功能 - 模型定义和基础操作。这是真正开始写业务代码的第一步,敬请期待!


相关文章推荐:

有问题欢迎留言讨论,我会及时回复大家!