Фон
Во время моей недавней разработки функции синхронизации данных с использованием метода bulkCreate
в Sequelize я столкнулся с проблемой неверных возвращаемых идентификаторов при использовании параметра updateOnDuplicate: true
. Это заставило меня заподозрить, что Sequelize генерирует возвращаемые идентификаторы посредством логического вывода. Чтобы подтвердить свою гипотезу, я решил исследовать исходный код Sequelize.
Как Sequelize генерирует возвращаемые идентификаторы в bulkCreate
Несмотря на обширную кодовую базу Sequelize, найти метод bulkCreate
было несложно. Глобальный поиск в кодовой базе привел меня прямо к файлу src/model.js
, где я нашел соответствующий комментарий, подтверждающий мое первоначальное предположение.
/* * The success handler is passed an array of instances, but please notice that these may not completely represent the state of the rows in the DB. This is because MySQL * and SQLite do not make it easy to obtain back automatically generated IDs and other default values in a way that can be mapped to multiple records. * To obtain Instances for the newly created values, you will need to query for them again. */
Затем я приступил к изучению этого метода, следуя приведенным ниже фрагментам кода:
// src/model.js const results = await model.queryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes); static get queryInterface() { return this.sequelize.getQueryInterface(); } // src/sequelize.js this.queryInterface = this.dialect.queryInterface; // src/dialects/sqlite/index.js // Just take SQLite as an example. this.queryInterface = new SQLiteQueryInterface( sequelize, this.queryGenerator ); // src/dialects/sqlite/query-interface.js class SQLiteQueryInterface extends QueryInterface { // ... } // src/dialects/abstract/query-generator.js async bulkInsert(tableName, records, options, attributes) { options = { ...options }; options.type = QueryTypes.INSERT; const results = await this.sequelize.query( this.queryGenerator.bulkInsertQuery(tableName, records, options, attributes), options ); return results[0]; } // src/sequelize.js async query(sql, options) { // ... const query = new this.dialect.Query(connection, this, options); // ... return await query.run(sql, bindParameters); // ... } // src/dialects/sqlite/query.js async run(sql, parameters) { // ... resolve(query._handleQueryResponse(this, columnTypes, executionError, results, errForStack.stack)); // ... }
И, наконец, я получил то, к чему стремился:
// src/dialects/sqlite/query.js _handleQueryResponse(metaData, columnTypes, err, results, errStack) { // ... // add the inserted row id to the instance if (this.isInsertQuery(results, metaData) || this.isUpsertQuery()) { this.handleInsertQuery(results, metaData); if (!this.instance) { // handle bulkCreate AI primary key if ( metaData.constructor.name === 'Statement' && this.model && this.model.autoIncrementAttribute && this.model.autoIncrementAttribute === this.model.primaryKeyAttribute && this.model.rawAttributes[this.model.primaryKeyAttribute] ) { const startId = metaData[this.getInsertIdField()] - metaData.changes + 1; result = []; for (let i = startId; i < startId + metaData.changes; i++) { result.push({ [this.model.rawAttributes[this.model.primaryKeyAttribute].field]: i }); } } else { result = metaData[this.getInsertIdField()]; } } } // ... }
При использовании метода _handleQueryResponse
стало очевидно, что Sequelize использует последний вставленный идентификатор и выводит оставшиеся идентификаторы при использовании bulkCreate
. Следовательно, при использовании опции updateOnDuplicate: true
есть вероятность получения неверных идентификаторов в возврате.
Полезный совет для получения точных идентификаторов
Улучшите таблицу данных, добавив полеbatch
, назначив номер партии каждому набору вставляемых данных. Впоследствии вы можете запросить вставленные идентификаторы, используя соответствующий номер пакета.
Риск использования updateOnDuplicate: true
При использовании параметра updateOnDuplicate: true
я заметил, что хотя повторяющиеся строки пропускаются, ключ autoIncrement
продолжает увеличиваться. Из документации MySQL сказано:
Эффекты не совсем идентичны: для таблицы InnoDB, где a является столбцом с автоинкрементом, инструкция INSERT увеличивает значение автоинкремента, а UPDATE — нет.
База данных следует за процессом попытки вставки, увеличения идентификатора, а затем обнаруживается дубликат. Однако после автоматического увеличения идентификатора его нельзя будет откатить.
Эта ситуация представляет потенциальный риск при использовании архитектуры базы данных master-slave. Поскольку идентификатор автоматически увеличивается, но обнаруживается дубликат, операция не оставляет журналов бинов, существует вероятность возникновения конфликтов ключей, когда ведущий выходит из строя, а подчиненный становится новым ведущим.