关于Nodejs的sqlite3模块的疑问

关于Nodejs的sqlite3模块的疑问

nodejs新手,最近用nodejs练手,写了个文件分析的程序。大体逻辑是:

1 用file-utils模块列举某个目录下所有文件 2 如果是html文件,抽取其中的内容(本为异步抽取,为了简便代码中写成同步模式) 3 【关键】将内容(content为数组,其元素是数组)调用saveReport函数,保存到sqlite数据库中。原意是希望插入cover_report后,立刻查到cover_report自动生成的id,然后再根据文本内容插入到raw_data(content的长度不固定)。

如果是一般的程序开发,应该用同一个事物保证这件事情的顺序性,但在nodejs环境下,一切皆异步,很难保证顺序。虽然sqlite保证生成cover_report数据会先于raw_data,但是无法按照cover_report->raw_data->cover_report->raw_data…这样的顺序插入。最后导致的结果是cover_report->cover_report->cover_report->raw_data->raw_data->raw_data

这有什么问题呢?目录下的文件有很多,sqlite又不是立刻写入的,因此运行了半天数据库一条记录都没有,一旦程序蹦了就全没了(这也许也是sqlite3模块的问题)。

不知道各位高手有什么办法保证事务呢?有什么号的办法呢?

var sqlite3 = require(‘sqlite3’).verbose(); var db = new sqlite3.Database(‘flaws.sqlite’);

function saveReport(path, filename, content) { // console.log(“SAVE REPORT:” + path); db.serialize(function() { var stmt = db.prepare(“INSERT INTO cover_report(filename,path) VALUES (?,?);”); stmt.run([filename, path]); stmt.finalize();

	console.log("COVER REPORT:" + path);
	db.each("SELECT id FROM cover_report where path = ?", [path], function(err, row) {
		var id = row.id;
		console.log("GET ID:" + id);
		var stmt2 = db.prepare("INSERT INTO raw_data(flawno,flawid,title,degree,url,fix,param,filename,reportid) VALUES (?,?,?,?,?,?,?,?,?)");
		for(var i = 0, len = content.length; i < len; ++i) {
			content[i].push(filename);
			content[i].push(id);
			stmt2.run(content[i]);
		}
		stmt2.finalize();
	});
});

}

function listTarget(root) { var fileUtils = require(“file-utils”); var File = fileUtils.File;

new File(root).list(function(name, path) {
//console.log(name);
var index = name.indexOf(".html");
if(index &gt;= 0) {
	//其实是异步抽取,为了简便所以简化为同步
	var content = extract(name); 
	saveReport(path, name, content);
}
return true;

}, 10, function() { });

}

listTarget(“c:/”);


4 回复

针对您的需求,您可以使用sqlite3模块的事务功能来确保数据的一致性和顺序性。以下是改进后的代码示例:

var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database('flaws.sqlite');

db.serialize(function() {
    // 开始一个新的事务
    db.run("BEGIN TRANSACTION");

    function saveReport(path, filename, content) {
        var stmt = db.prepare("INSERT INTO cover_report(filename,path) VALUES (?,?);");
        stmt.run([filename, path], function(err) {
            if (err) {
                console.error("Error inserting cover_report:", err);
                return;
            }
            // 获取刚刚插入的cover_report的ID
            db.get("SELECT last_insert_rowid() AS id", [], function(err, row) {
                if (err) {
                    console.error("Error getting last_insert_rowid:", err);
                    return;
                }

                var id = row.id;

                // 插入raw_data
                var stmt2 = db.prepare("INSERT INTO raw_data(flawno, flawid, title, degree, url, fix, param, filename, reportid) VALUES (?,?,?,?,?,?,?,?,?)");
                for (var i = 0, len = content.length; i < len; ++i) {
                    content[i].push(filename);
                    content[i].push(id);
                    stmt2.run(content[i]);
                }
                stmt2.finalize();

                // 提交事务
                db.run("COMMIT TRANSACTION", function(err) {
                    if (err) {
                        console.error("Error committing transaction:", err);
                        return;
                    }
                    console.log("Transaction committed successfully.");
                });
            });
        });

        stmt.finalize();
    }

    function listTarget(root) {
        var fileUtils = require('file-utils');
        var File = fileUtils.File;

        new File(root).list(function(name, path) {
            var index = name.indexOf(".html");
            if (index >= 0) {
                // 实际上是异步抽取,为了简便所以简化为同步
                var content = extract(name);
                saveReport(path, name, content);
            }
            return true;
        }, 10, function() {});
    }

    listTarget("c:/");
});

解释:

  1. 事务管理:通过BEGIN TRANSACTION开始一个事务,并在所有操作完成后使用COMMIT TRANSACTION提交事务。这样可以确保所有操作要么全部成功,要么全部失败。
  2. 获取自增ID:使用last_insert_rowid()获取刚刚插入的cover_report记录的ID。
  3. 插入raw_data:在获取到cover_report的ID之后,再插入相应的raw_data记录。

这种方法可以确保数据的一致性和顺序性,避免因异步操作导致的数据混乱。


楼主的nodejs是运行在什么操作系统之上的呢? 是windows吗? 我在windows上npm install sqlite3装不上啊? 求助!(https://github.com/developmentseed/node-sqlite3)

请问楼主的问题最终是如何解决的呢?我在使用sqlit3保存聊天记录时也发生了插入结果的顺序与调用插入接口的顺序不一致的现象。请问你是如何实现使插入数据结果的顺序与调用插入接口的顺序已知的呢?

在 Node.js 中使用 sqlite3 模块处理数据库操作时,需要特别注意异步操作的顺序性和事务管理。从你的描述来看,你遇到了一些挑战,比如如何确保插入 cover_report 表后立即获取该记录的 id,并基于此 id 插入 raw_data 表。

为了解决这些问题,你可以考虑使用事务来保证操作的原子性和顺序性。以下是修改后的代码示例,展示了如何使用事务来解决这个问题:

const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('flaws.sqlite');

function saveReport(path, filename, content) {
    db.serialize(() => {
        db.run(`BEGIN TRANSACTION`, err => {
            if (err) return console.error(err.message);

            // 插入 cover_report
            const insertCoverStmt = db.prepare(`
                INSERT INTO cover_report(filename, path)
                VALUES (?, ?)
            `);
            insertCoverStmt.run([filename, path], (insertErr) => {
                if (insertErr) return console.error(insertErr.message);

                // 获取刚刚插入的 cover_report 的 id
                const selectIdStmt = db.prepare(`
                    SELECT last_insert_rowid() AS id
                `);
                selectIdStmt.get((selectErr, row) => {
                    if (selectErr) return console.error(selectErr.message);

                    const id = row.id;

                    // 插入 raw_data
                    const insertRawStmt = db.prepare(`
                        INSERT INTO raw_data(flawno, flawid, title, degree, url, fix, param, filename, reportid)
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                    `);
                    content.forEach(item => {
                        item.push(filename);
                        item.push(id);
                        insertRawStmt.run(item);
                    });

                    // 提交事务
                    db.run(`COMMIT TRANSACTION`, err => {
                        if (err) return console.error(err.message);
                        console.log(`Data saved successfully for ${filename}`);
                    });
                });
            });
        });
    });
}

function listTarget(root) {
    const fileUtils = require('file-utils');
    const File = fileUtils.File;

    new File(root).list((name, path) => {
        const index = name.indexOf('.html');
        if (index >= 0) {
            const content = extract(name);
            saveReport(path, name, content);
        }
        return true;
    }, 10, () => {});
}

listTarget('c:/');

关键点解释:

  1. 事务管理:使用 BEGIN TRANSACTIONCOMMIT TRANSACTION 确保一系列操作在一个事务中完成。
  2. 获取 id:使用 last_insert_rowid() 获取刚刚插入的记录的 id
  3. 插入 raw_data:在获取到 id 后,再插入 raw_data 记录。
  4. 异常处理:每个步骤都包含了错误处理,以确保异常情况能够被及时捕获和处理。

通过这种方式,你可以确保数据的一致性和顺序性,避免因为异步操作导致的数据丢失或不一致问题。

回到顶部