1. 程式人生 > >PHP使用Sqlite3批量插入調優

PHP使用Sqlite3批量插入調優

工作中用到SQLite,簡單好用,但是存在多個程序寫同一個db檔案時的鎖問題,報database is locked錯誤導致插入失敗。

業務中存在批量插入的場景,這裡按之前MySQL的優化思路優化了SQLite的插入,下面是一些測試用例。

下面的測試用列是在我虛擬機器上執行的,絕對資料沒什麼意義,可以對比看各個方案的執行效率。

<?php
class TestSqlite {
    private $db = null;
    const TABLE_NAME = "t_test";
    const NUM = 500;

    public function __construct($file_data)
    {
        $this->db = new \SQLite3($file_data);
        if (!$this->db) {
            throw new \Exception("SQLite3 construct failed,err:" . $this->db->lastErrorMsg());
        }

        $this->init();
    }

    private function init()
    {
        $table_name = self::TABLE_NAME;

        $sql =<<<EOF
      CREATE TABLE IF NOT EXISTS $table_name 
      (id INTEGER PRIMARY KEY AUTOINCREMENT,
       type int NOT NULL DEFAULT 0,
       action int NOT NULL DEFAULT 0,
       data TEXT NOT NULL,
       create_time datetime NOT NULL
      );
EOF;
        $ret = $this->db->exec($sql);
        if(!$ret) {
            echo("insert failed,err:". $this->db->lastErrorMsg().PHP_EOL);
        }
    }

    public function test($callable)
    {
        $this->db->exec("delete from ".self::TABLE_NAME);
        $tickStart = microtime(true);
        call_user_func_array($callable,[]);
        echo "cost ".(microtime(true)-$tickStart)." ms".PHP_EOL;
    }

    public function forInsert()
    {
        for($i=0;$i<self::NUM;$i++)
        {
            $ret = $this->db->exec("INSERT INTO ".self::TABLE_NAME."(id,type,action,data,create_time) VALUES(NULL,".$i.",".$i.",".$i.",datetime('now','localtime'))");
            if(!$ret) {
                echo("insert failed,err:" . $this->db->lastErrorMsg().PHP_EOL);
            }
        }
    }

    public function forTrans()
    {
        $this->db->exec("begin;");
        for($i=0;$i<self::NUM;$i++)
        {
            $ret = $this->db->exec("INSERT INTO ".self::TABLE_NAME."(id,type,action,data,create_time) VALUES(NULL,".$i.",".$i.",".$i.",datetime('now','localtime'))");
            if(!$ret) {
                echo("insert failed,err:" . $this->db->lastErrorMsg().PHP_EOL);
            }
        }
        $this->db->exec("commit;");
    }


    public function forSync()
    {
        $this->db->exec("PRAGMA synchronous = OFF;");
        for($i=0;$i<self::NUM;$i++)
        {
            $ret = $this->db->exec("INSERT INTO ".self::TABLE_NAME."(id,type,action,data,create_time) VALUES(NULL,".$i.",".$i.",".$i.",datetime('now','localtime'))");
            if(!$ret) {
                echo("insert failed,err:" . $this->db->lastErrorMsg().PHP_EOL);
            }
        }
        $this->db->exec("PRAGMA synchronous = ON;");
    }

    public function forBind()
    {
        $bind_sql = "INSERT INTO ".self::TABLE_NAME."(id,type,action,data,create_time) VALUES(?,?,?,?,?)";
        $rs = $this->db->prepare($bind_sql);
        for($i=0;$i<self::NUM;$i++)
        {
            $rs->reset();
            $rs->bindValue(1,$i,SQLITE3_INTEGER);
            $rs->bindValue(2,$i,SQLITE3_INTEGER);
            $rs->bindValue(3,$i,SQLITE3_INTEGER);
            $rs->bindValue(4,$i,SQLITE3_TEXT);
            $rs->bindValue(5,$i);
        }
        $rs->execute();
    }
}

$s = new TestSqlite("/tmp/test.db");
$s->test(array($s,"forInsert"));
$s->test(array($s,"forTrans"));
$s->test(array($s,"forSync"));
$s->test(array($s,"forBind"));
cost 1.5027620792389 ms
cost 0.014527082443237 ms
cost 0.036828994750977 ms
cost 0.0043308734893799 ms

從這裡可以看出第四種方案是效能最好的。