写这个文章的目的
最近想学习下node语言下的orm框架,于是选择了Sequelize,他的中文文档很详细,网上相关的文章也比较多,但是,没有找到明确的关于数据库的连接与释放的内容,毕竟数据库连接很重要,如果你不知道数据库的连接是什么时候获取的,什么时候释放的,你程序再精妙也会让你有种不踏实的感觉。为了消除这种不踏实的感觉,因此写下这篇文章进行记录。
也就是说我需要消除这2点疑惑
- Sequelize的数据库连接是什么时候获取和释放的
new Sequelize()对象是否可以全局共用一个还是说每次使用时都需要new Sequelize()
Sequelize是什么
node语言下用于数据库开发的ORM框架
数据库开发相关概念
- 数据库
- 数据库连接
- 数据库连接池
- 表
- 字段
- 约束与索引
Sequelize为我们提供了哪些便利
进行数据库编程,大概需要经历这7个步骤
- 建库和建表
- 配置数据库连接信息
- 建立数据库连接或数据库连接池
- 拿到数据库连接然后开启事务
- 执行数据库操作相关代码(如: select xxx)
- 提交事务
- 关闭数据库连接或将数据库连接归还到数据库连接池
用例Sequelize之后,如何进行数据库编程
- 建表:通过
sequelize.define(xxx)自动建表 - 配置数据库连接信息:通过
const sequelize = new Sequelize(...)配置数据库连接信息 - 建立数据库连接或数据库连接池:sequelize在你执行数据库操作语句之前,自动帮你创建以及获取连接 [这一步是自动完成的,开发者无需考虑]
- 拿到数据库连接然后开启事务:sequelize有相关方法支持
- 执行数据库操作相关代码(如: select xxx):sequelize有相关方法支持
- 提交事务:sequelize在你的sql代码执行完成之后,自动帮你提交事务 [这一步是自动完成的,开发者无需考虑]
- 关闭数据库连接或将数据库连接归还到数据库连接池: [这一步是自动完成的,开发者无需考虑]
标注了这句话的[这一步是自动完成的,开发者无需考虑],3,6,7条,你有啥证明吗?不会是我yy出来的吧,有种不踏实的感觉,那么就必须从源码中找证据了
export class Sequelize {
constructor(database, username, password, options) {
// ... 省略一堆与本次问题不那么相关的代码
let Dialect;
// ... 省略一堆与本次问题不那么相关的代码
switch (this.getDialect()) {
case 'mariadb':
Dialect = require('./dialects/mariadb').MariaDbDialect;
break;
case 'mssql':
Dialect = require('./dialects/mssql').MssqlDialect;
break;
case 'mysql':
Dialect = require('./dialects/mysql').MysqlDialect;
break;
case 'postgres':
Dialect = require('./dialects/postgres').PostgresDialect;
break;
case 'sqlite':
Dialect = require('./dialects/sqlite').SqliteDialect;
break;
case 'ibmi':
Dialect = require('./dialects/ibmi').IBMiDialect;
break;
case 'db2':
Dialect = require('./dialects/db2').Db2Dialect;
break;
case 'snowflake':
Dialect = require('./dialects/snowflake').SnowflakeDialect;
break;
default:
throw new Error(`The dialect ${this.getDialect()} is not supported. Supported dialects: mariadb, mssql, mysql, postgres, sqlite, ibmi, db2 and snowflake.`);
}
this.dialect = new Dialect(this);
// ... 省略一堆与本次问题不那么相关的代码
/**
* Models are stored here under the name given to `sequelize.define`
*/
this.models = {};
this.modelManager = new ModelManager(this);
// 连接的获取与释放都是由connectionManager完成, 从后续的query代码节选中就可以看到connectionManager的使用
this.connectionManager = this.dialect.connectionManager;
Sequelize.runHooks('afterInit', this);
}
}
从github仓库可以看到 sequelize源码, 查看 sequelize.js的query方法源码
query方法代码节选
async query(sql, options) {
// ... 省略一堆对本次问题不重要的代码
// 定义检查事务的方法
const checkTransaction = () => {
if (options.transaction && options.transaction.finished && !options.completesTransaction) {
const error = new Error(`${options.transaction.finished} has been called on this transaction(${options.transaction.id}), you can no longer use it. (The rejected query is attached as the 'sql' property of this error)`);
error.sql = sql;
throw error;
}
};
// 定义重试配置
const retryOptions = { ...this.options.retry, ...options.retry };
// 执行重试方法,并将结果返回
return retry(async () => {
if (options.transaction === undefined && Sequelize._cls) {
options.transaction = Sequelize._cls.get('transaction');
}
// 检查事务
checkTransaction();
// 获取连接:如果存在事务则获取事务里的连接,如果不存在事务,则从connectionManager中获取
// ************************ 这段代码就是自动帮我们获取数据库连接
const connection = await (options.transaction ? options.transaction.connection : this.connectionManager.getConnection({
useMaster: options.useMaster,
type: options.type === 'SELECT' ? 'read' : 'write',
}));
if (this.options.dialect === 'db2' && options.alter && options.alter.drop === false) {
connection.dropTable = false;
}
const query = new this.dialect.Query(connection, this, options);
try {
await this.runHooks('beforeQuery', options, query);
checkTransaction();
// 执行sql语句并返回结果
return await query.run(sql, bindParameters);
} finally {
await this.runHooks('afterQuery', options, query);
if (!options.transaction) {
// 释放数据库连接
// ************************ 这段代码就是自动帮我们释放数据库连接
await this.connectionManager.releaseConnection(connection);
}
}
}, retryOptions);
}
Sequelize文档中有提到close方法,那这是多此一举?
close方法用于关闭所有数据库连接。等等,不对劲,你上面不是说数据库的连接与关闭都是Sequelize自动完成的吗?为啥这里又搞个方法用于手动关闭,并且还是关闭所有的数据库连接?
从上面的节选代码中可以看出,实际的数据库连接获取与释放是由connectionManager内部完成的。当我们使用了数据库连接池时,connectionManager.releaseConnection(connection) 方法内部,可能只是将连接归还了连接池,实际并没有真正意义的关闭。但我们的数据库连接最终肯定是要真正关闭的。
所以,这就是close方法的作用,就是在你的服务关闭或应用退出时,通过此方法,将数据库连接全部正常关闭
/**
* Close all connections used by this sequelize instance, and free all references so the instance can be garbage collected.
*
* Normally this is done on process exit, so you only need to call this method if you are creating multiple instances, and want
* to garbage collect some of them.
*
* @returns {Promise}
*/
close() {
return this.connectionManager.close();
}
总结
到这里,一开始抛出的两个疑问已经有了结论
-
Sequelize的数据库连接是什么时候获取和释放的 在执行Sequelize提供的数据库操作的相关方法中,方法内部会自动帮你获取/释放数据库连接资源
-
new Sequelize()对象是否可以全局共用一个还是说每次使用时都需要new Sequelize()之所以有这个疑问还是因为将new Sequelize(xxx)的代码,看作了java中的new Connection()代码,所以对Sequelize对象能否全局公用一个存有怀疑,但从上面的源码中可以得知,new Sequelize并不是java中new Connection(),因此完全可以全局使用同一个Sequelize对象,并且只能全局共用一个Sequelize对象,不然就会产生多个connectionManager, 数据库连接数量可能与你的预期不符,且事务控制也可能出现问题