1. Bối cảnh
sequelize
Nó là một khung dựa trên promise
cơ sở dữ liệu quan hệ Node.js
ORM
, cung cấp các chức năng như hỗ trợ thiết lập cấu trúc mô hình, kết nối và đóng gói các chức năng vận hành cơ sở dữ liệu, sequelize-typescript
nhưng khung typescript
dựa trên sự đóng gói để hỗ trợ cú pháp sequelize
.
sequelize-typescript
Do sự tiện lợi của việc bảo trì cơ sở dữ liệu trong giai đoạn đầu của dự án, chức năng được cung cấp sẽ được gọi khi dịch vụ ứng dụng bắt đầu sync
tự động đồng bộ hóa với cơ sở dữ liệu theo cấu trúc mô hình, không có vấn đề gì lớn trong việc đồng bộ hóa các bảng thông thường theo cách này , nhưng sync
chức năng hiện tại không hỗ trợ đồng bộ hóa các bảng phân vùng, dẫn đến trường hợp các bảng phân vùng chỉ có thể được tạo và duy trì theo cách thủ công.
Do đó, để giải quyết vấn đề trên, chúng ta chỉ có thể sequelize-typescript
thực hiện chức năng tự động đồng bộ hóa bảng phân vùng dựa trên nó, tiếp theo chúng ta sẽ thảo luận về sequelize-typescript
sơ đồ và triển khai mở rộng nó để hỗ trợ bảng phân vùng.
Trước tiên, hãy hiểu sequelize
cách cấu trúc mô hình được đồng bộ hóa với cơ sở dữ liệu thông qua mã nguồn, để sửa đổi nó, trước tiên hãy xem cách sequelize.sync
triển khai chức năng:
// 文件路径为<project_path>/node_modules/[email protected]@sequelize/lib/sequelize.js
// 路径中的"4.44.2"是sequelize-typescript库引用的sequelize版本号,可能sequelize-typescript版本不同使得引用的sequelize版本有所差异
sync(options) {
options = _.clone(options) || {};
options.hooks = options.hooks === undefined ? true : !!options.hooks;
options = _.defaults(options, this.options.sync, this.options);
if (options.match) {
if (!options.match.test(this.config.database)) {
return Promise.reject(new Error(`Database "${this.config.database}" does not match sync match parameter "${options.match}"`));
}
}
return Promise.try(() => {
if (options.hooks) {
return this.runHooks('beforeBulkSync', options);
}
}).then(() => {
if (options.force) {
return this.drop(options);
}
}).then(() => {
const models = [];
// Topologically sort by foreign key constraints to give us an appropriate
// creation order
this.modelManager.forEachModel(model => {
if (model) {
models.push(model);
} else {
// DB should throw an SQL error if referencing inexistant table
}
});
return Promise.each(models, model => model.sync(options));
}).then(() => {
if (options.hooks) {
return this.runHooks('afterBulkSync', options);
}
}).return(this);
}
Có thể thấy từ đoạn mã trên, sequelize.sync
ngoài việc thực hiện một số hook
thao tác phía trước và phía sau trong chức năng, nó còn gọi sync
cấu trúc bảng đồng bộ hóa chức năng của từng mô hình, sau đó dán model.sync
mã chức năng:
// 文件路径为<project_path>/node_modules/[email protected]@sequelize/lib/model.js
// 路径中的"4.44.2"是sequelize-typescript库引用的sequelize版本号,可能sequelize-typescript版本不同使得引用的sequelize版本有所差异
static sync(options) {
options = _.extend({}, this.options, options);
options.hooks = options.hooks === undefined ? true : !!options.hooks;
const attributes = this.tableAttributes;
return Promise.try(() => {
if (options.hooks) {
return this.runHooks('beforeSync', options);
}
}).then(() => {
if (options.force) {
return this.drop(options);
}
})
.then(() => this.QueryInterface.createTable(this.getTableName(options), attributes, options, this))
.then(() => {
if (options.alter) {
return Promise.all([
this.QueryInterface.describeTable(this.getTableName(options)),
this.QueryInterface.getForeignKeyReferencesForTable(this.getTableName(options))
])
.then(tableInfos => {
const columns = tableInfos[0];
// Use for alter foreign keys
const foreignKeyReferences = tableInfos[1];
const changes = []; // array of promises to run
const removedConstraints = {};
_.each(attributes, (columnDesc, columnName) => {
if (!columns[columnName]) {
changes.push(() => this.QueryInterface.addColumn(this.getTableName(options), columnName, attributes[columnName]));
}
});
_.each(columns, (columnDesc, columnName) => {
const currentAttributes = attributes[columnName];
if (!currentAttributes) {
changes.push(() => this.QueryInterface.removeColumn(this.getTableName(options), columnName, options));
} else if (!currentAttributes.primaryKey) {
// Check foreign keys. If it's a foreign key, it should remove constraint first.
const references = currentAttributes.references;
if (currentAttributes.references) {
const database = this.sequelize.config.database;
const schema = this.sequelize.config.schema;
// Find existed foreign keys
_.each(foreignKeyReferences, foreignKeyReference => {
const constraintName = foreignKeyReference.constraintName;
if (!!constraintName
&& foreignKeyReference.tableCatalog === database
&& (schema ? foreignKeyReference.tableSchema === schema : true)
&& foreignKeyReference.referencedTableName === references.model
&& foreignKeyReference.referencedColumnName === references.key
&& (schema ? foreignKeyReference.referencedTableSchema === schema : true)
&& !removedConstraints[constraintName]) {
// Remove constraint on foreign keys.
changes.push(() => this.QueryInterface.removeConstraint(this.getTableName(options), constraintName, options));
removedConstraints[constraintName] = true;
}
});
}
changes.push(() => this.QueryInterface.changeColumn(this.getTableName(options), columnName, attributes[columnName]));
}
});
return changes.reduce((p, fn) => p.then(fn), Promise.resolve());
});
}
})
.then(() => this.QueryInterface.showIndex(this.getTableName(options), options))
.then(indexes => {
// Assign an auto-generated name to indexes which are not named by the user
this.options.indexes = this.QueryInterface.nameIndexes(this.options.indexes, this.tableName);
indexes = _.filter(this.options.indexes, item1 =>
!_.some(indexes, item2 => item1.name === item2.name)
).sort((index1, index2) => {
if (this.sequelize.options.dialect === 'postgres') {
// move concurrent indexes to the bottom to avoid weird deadlocks
if (index1.concurrently === true) return 1;
if (index2.concurrently === true) return -1;
}
return 0;
});
return Promise.map(indexes, index => this.QueryInterface.addIndex(
this.getTableName(options),
_.assign({
logging: options.logging,
benchmark: options.benchmark,
transaction: options.transaction
}, index),
this.tableName
));
}).then(() => {
if (options.hooks) {
return this.runHooks('afterSync', options);
}
}).return(this);
}
model.sync
Đoạn mã này hơi dài và quy trình thực thi chung như sau:
Hai, chương trình
Sau khi hiểu quy trình thực hiện đồng bộ hóa cấu trúc mô hình với cơ sở dữ liệu, trước tiên hãy thiết kế một cách để hỗ trợ định nghĩa bảng phân vùng.Giải pháp của tôi là thêm một số thuộc tính mới trong mục cấu hình của bảng định nghĩa: , , tương ứng đại diện cho phân vùng loại, trường partition
phân vùng , quy tắc phân vùng.partitionKey
partitionRule
Trong số đó , ba loại partition
được hỗ trợ ; một , khóa là hậu tố của tên bảng phụ phân vùng và số lượng khóa là số bảng phụ phân vùng và giá trị có thể tương ứng là khoảng/ danh sách giá trị/ giá trị ánh xạ; phân vùng được tạo tên bảng phụ được xác định bởi bảng chính Mỗi khóa trong được tạo.RANGE
LIST
HASH
partitionRule
object
RANGE
LIST
HASH
partitionRule
Sau đó, bạn cần viết lại sync
chức năng của bảng phân vùng và quy trình thực hiện chính như sau:
Vì phiên bản Postgresql 10.x được sử dụng khi mở rộng phần tiếp theo, chúng ta cần hiểu sự khác biệt giữa các bảng phân vùng được hỗ trợ bởi phiên bản Postgresql 10.x và Postgresql 11.x:
- Postgresql 10.x chỉ hỗ trợ các phân vùng RANGE và LIST, trong khi Postgresql 11.x cũng hỗ trợ các phân vùng HASH;
- Bảng chính Postgresql 10.x không hỗ trợ tạo khóa/chỉ mục chính và chỉ mục của từng bảng phụ phải được vận hành riêng biệt, trong khi Postgresql 11.x có thể trực tiếp vận hành chỉ mục của bảng chính và sẽ tự động đồng bộ hóa với từng bảng bàn phụ;
- Postgresql 10.x không có phân vùng mặc định và khi dữ liệu được chèn vào không thể tìm thấy phân vùng tương ứng, quá trình chèn sẽ không thành công, trong khi Postgresql 11.x hỗ trợ phân vùng mặc định;
- Postgresql 10.x chỉ hỗ trợ phân vùng một cấp, tức là không thể phân vùng lại các bảng con, trong khi Postgresql 11.x hỗ trợ phân vùng nhiều cấp;
Ba, nhận ra
Tiếp theo, việc triển khai cụ thể một số quy trình chính (được biểu thị bằng màu tối trong sơ đồ) sẽ được mô tả chi tiết, điều đáng nói là chức năng viết lại cú pháp sẽ Postgresql 10.x
được sử dụng dưới tiền đề hỗ trợ phiên bản .typescript
model.sync
Mã sau đây sẽ được đăng trong các phần để giải thích và mã hoàn chỉnh có thể được truy xuất trên github .
1. Tạo bảng tổng thể phân vùng mới
Bước này cần tạo một bảng tổng thể phân vùng mới thay vì một bảng chung, vì vậy nó cần được sql
sửa đổi trên cơ sở bảng đã tạo ban đầu, cách thực hiện cụ thể là gọi createTableQuery
bảng chung đã tạo và thêm nó sql
vào cuối để tạo một bảng bảng chính được phân vùng thông qua thực thi trực tiếp.sql
PARTITION BY <partition> (<partitionKey>)
// 生成创建分区主表的SQL
const attrs = this.QueryInterface.QueryGenerator.attributesToSQL(attributes, {
context: 'createTable',
});
let createTableSql = this.QueryInterface.QueryGenerator.createTableQuery(this.getTableName(options), attrs, options);
createTableSql = createTableSql.substring(0, createTableSql.length - 1) + ` PARTITION BY ${options.partition} ("${options.partitionKey}");`;
return this.sequelize.query(createTableSql, options);
Tuy nhiên, cần lưu ý trong liên kết này rằng do Postgresql 10.x
phiên bản của bảng chính phân vùng không hỗ trợ tạo khóa chính và chỉ mục, nên cần xóa khóa chính trước khi tạo bảng chính phân vùng sql
và nên sử dụng một chỉ mục duy nhất thay vì khóa chính trong bảng phụ của phân vùng.// postgresql 10.x版本中分区主表不能有主键和索引
const primaryKey: string[] = [];
for (const name in attributes) {
const attr = attributes[name];
if (attr.primaryKey) {
primaryKey.push(name);
attr.allowNull = false;
delete attr.primaryKey;
}
if (name === this.options.partitionKey) {
prititionColumn = attributes[name];
}
}
// 分区子表使用unique索引代替主键
if (!this.options.indexes) {
this.options.indexes = [];
}
this.options.indexes.push({
fields: primaryKey,
unique: true,
});
2. Tạo một bảng phụ phân vùng mới có hậu tố với mỗi khóa của partitionRule
Ở bước này, bạn chỉ cần ghép các bảng con theo cách phân vùng và tên của bảng chính được phân vùng sql
, vì các bảng con sẽ tự động thống nhất với cấu trúc trường của bảng chính nên việc tạo sql
tương đối đơn giản. Bởi vì nó đi qua mọi lúc partitionRule
, một bảng con mới có thể được tạo tự động khi các giá trị khóa và quy tắc phân vùng mới được thêm vào
// 同步分区子表
const pmList: any = [];
for (const suffix in options.partitionRule) {
let rule = options.partitionRule[suffix];
if (prititionColumn.type instanceof DataType.STRING || prititionColumn.type instanceof DataType.TEXT || prititionColumn.type instanceof DataType.CHAR) {
rule = rule.map(val => `'${val}'`);
}
let sql = `CREATE TABLE IF NOT EXISTS "${this.tableName + suffix}" PARTITION OF "${this.tableName}" FOR VALUES`;
if (options.partition.toUpperCase() === 'LIST') {
sql += ` IN (${rule.join(',')});`;
} else if (options.partition.toUpperCase() === 'RANGE') {
sql += ` FROM (${rule[0]}) TO (${rule[1]});`;
}
pmList.push(this.sequelize.query(sql, options));
}
return Promise.all(pmList);
3. So sánh và thêm, xóa và sửa đổi các trường trong bảng tổng thể phân vùng
Vì việc thêm, xóa và sửa đổi các trường của bảng tổng thể phân vùng sẽ tự động đồng bộ hóa các thay đổi đối với từng bảng phụ của phân vùng, nên bước này chỉ cần thao tác trực tiếp với bảng chính của phân vùng và vì về cơ bản không có sự khác biệt giữa các thao tác logic của phân vùng bảng chính và các bảng thông thường, bước này về cơ bản giống như sync
thực hiện hàm gốc.
Tuy nhiên, Postgresql
sau khi bảng phân vùng được tạo, không được phép sửa đổi trường phân vùng, vì vậy changeColumn
trường phân vùng cần được loại trừ khi gọi.
if (columnName !== options.partitionKey) {
changes.push(() => this.QueryInterface.changeColumn(this.getTableName(options), columnName, attributes[columnName]));
}
4. Thêm chỉ mục subtable phân vùng
Postgresql
Không thể tạo chỉ mục cho bảng chính được phân vùng, do đó, ở đây cần duyệt qua từng bảng phụ được phân vùng, truy vấn các chỉ mục hiện có của từng bảng phụ từ db và tạo chỉ mục mới được thêm cho mỗi bảng phụ, nhưng cần lưu ý rằng tên chỉ mục phải là phụ Tên bảng phải được tạo thay vì tên bảng chính, nếu không sẽ xảy ra xung đột tạo chỉ mục.
// 同步每个分区子表的索引结构
const tableNameList: string[] = [];
let indexOptions: any = this.options.indexes;
for (const suffix in options.partitionRule) {
tableNameList.push(`${this.getTableName(options) + suffix}`);
}
return Promise.map(tableNameList, tableName => this.QueryInterface.showIndex(tableName, options))
.then(indexesList => {
const createIdxPrmList: any = [];
for (let i = 0 ; i < indexesList.length; i++) {
let indexes = indexesList[i];
const tableName = tableNameList[i];
for (const index of indexOptions) {
delete index.name;
}
indexOptions = this.QueryInterface.nameIndexes(indexOptions, tableName);
indexes = _.filter(indexOptions, item1 =>
!_.some(indexes, item2 => item1.name === item2.name),
).sort((index1, index2) => {
if (this.sequelize.options.dialect === 'postgres') {
// move concurrent indexes to the bottom to avoid weird deadlocks
if (index1.concurrently === true) return 1;
if (index2.concurrently === true) return -1;
}
return 0;
});
for (const index of indexes) {
createIdxPrmList.push(this.QueryInterface.addIndex(
tableName,
_.assign({
logging: options.logging,
benchmark: options.benchmark,
transaction: options.transaction,
}, index),
tableName,
));
}
}
return Promise.all(createIdxPrmList);
});
4. Kết luận
Bài viết về cơ bản nói về cách mở rộng lược sequelize
đồ và triển khai hỗ trợ đồng bộ hóa cấu trúc bảng phân vùng với cơ sở dữ liệu, nhưng không nói cách đọc và ghi các thao tác trên bảng phân vùng hiện có. lưu trữ vật lý và logic hoạt động là thống nhất, hầu như không có sự khác biệt giữa đọc và ghi bên ngoài và các bảng thông thường, vì vậy sequelize
hầu hết các chức năng được đóng gói có thể được áp dụng trực tiếp cho hoạt động của các bảng phân vùng.
Vậy thì tại sao sequelize
không cung cấp chức năng đồng bộ hóa cấu trúc bảng phân vùng với cơ sở dữ liệu? Tôi nghĩ rằng có một số lý do có thể:
- Bản thân việc sử dụng
sequelize.sync
để đồng bộ hóa cấu trúc bảng không phải làsequelize
mục đích ban đầu của việc tự động duy trì cấu trúc bảng, sau khi dự án dần phát triển,sequelize-cli
thư viện sẽ được sử dụng để duy trì các thay đổi của cấu trúc bảng cơ sở dữ liệu một cách riêng biệt;
-sequelize.sync
Thời gian thực hiện đồng bộ hóa cấu trúc bảng sẽ lâu hơn và có thể đồng bộ hóa một bảng với số lượng lớn phân vùng. Nó ảnh hưởng trực tiếp đến các hoạt động IO khác của toàn bộ cơ sở dữ liệu và dữ liệuPostgresql 11.x
hiện có cần được tự động di chuyển khi quy tắc phân vùng của phiên bản được thay đổi, điều này sẽ mang lại tác động hiệu suất lớn hơn; - Mức độ ứng dụng của các bảng phân vùng không rộng lắm, chủ yếu là do trong hầu hết các tình huống, khi lượng dữ liệu tăng lên, hiệu suất có xu hướng giảm nhanh chóng, do việc lưu trữ các bảng phân vùng được phân vùng, nhưng logic hoạt động bên ngoài được cung cấp là như nhau như của các bảng thông thường, tức là bản thân cơ sở dữ liệu phải thực hiện nhiều công việc hơn, chẳng hạn như định vị các phân vùng, tích hợp dữ liệu, v.v., và thậm chí trong một số trường hợp, cần phải quét từng bảng phụ. , bảng phân vùng không linh hoạt và dễ kiểm soát như sử dụng bảng phân vùng vật lý;
Vì hiệu suất của bảng phân vùng không lý tưởng, tại sao lại sử dụng bảng phân vùng hoặc bảng phân vùng có thể được xem xét trong trường hợp nào?
Tôi nghĩ rằng trong giai đoạn đầu của một số dự án, vẫn chưa có thư viện bảng phụ vật lý hoàn chỉnh, lượng dữ liệu trực tuyến sẽ trở nên lớn trong một khoảng thời gian ngắn và trong trường hợp có nhiều truy vấn phức tạp trên những dữ liệu này, bạn có thể cân nhắc sử dụng nó trước.
Bởi vì trong giai đoạn đầu của dự án, có thể có một yêu cầu có vẻ đơn giản dựa trên truy vấn + dữ liệu bảng lớn, thường được thực hiện bằng cách sử dụng các bảng con vật lý kết hợp với các bảng dự phòng dữ liệu tương ứng. để giảm chi phí phát triển và xem xét tiến độ dự án offset
,limit
Tác giả: Billlllllllly
Link: https://www.jianshu.com/p/7701a1d8fe28
Nguồn: Bản quyền của Jianshu
thuộc về tác giả. In lại với mục đích thương mại vui lòng liên hệ tác giả để được ủy quyền, in lại phi thương mại vui lòng ghi rõ nguồn.