sqlite3:鎖機制、stmt加速、wal日誌模式、多進程併發、寫互斥

最近需要做sqlite的併發優化,會有一些多主機多進程的操作失敗問題,所以學習一下,順便爲了翻閱,做一個筆記收集。

 

未完成。。。。。。。。。。。。。。。。。。。

to be continued

目前只對我某時刻最關注的點做筆記,默認簡單的就跳過了。

工作和時間原因,順序有些亂,可能隨時遇到問題就插進去了。

 

鎖機制與事務類型

https://www.cnblogs.com/lijingcheng/p/4454884.html

sqlite3源代碼註釋說明

等等等。。。

關鍵點

讀share、寫(內存)reserve、寫(磁盤)exclusive。(問題:reserve)

share:池大小可以控制。池裏得到一個字節,就算佔了一個位置,一個鎖(既然權限這麼低,還鎖什麼鎖?主要就是一方面可能池小能控制讀,節省資源?另一方面應該是爲了後續那幾個操作可以安全進行,所以這算一個flag性質吧)。

reserved:只有一個,其他的可以拿share,高級的可能不行了。

pending:現有share還在,不能再拿了,類似要提交的狀態了,等其他的鎖都放掉,我就可以寫磁盤了?也就是升級exclusive?(pending可以升級exclusive——sqlite3OsLock())

exclusive:完全互斥所有。(感覺少寫幾次磁盤纔有性能,wal可以(但是跨主機共享的話,wal不能用)?transaction和stmt可以?stmt好像是省了編譯過程,沒說省寫入過程)

鎖的操作系統支持

LockFile()、UnLockFile()、LockFileEx()、UnLockFileEx(),理論可以跨linux和win。理論可以安全跨系統跨進程共享數據庫。(WAL例外吧,WAL加速依賴的是系統緩存,跨系統怎麼能共享內存呢?!!所以跨系統應該關閉WAL

這裏細節也不用太關注,一般都是高級系統了,舊系統功能稍微低級點,鎖的情況更多

 

 

死鎖的例子:

前邊的帖子把insert詳細的和reserved鎖綁定了,說提交是exclusive,說兩個事務都打開,如果兩個終端都insert,會死鎖,誰都不成功,但是我實測如下(只代表默認環境和默認命令)——如果第二個insert執行了但未commit,第一個再insert是失敗的,而不是兩個都是insert執行等待commit。

開始

1:begin

2:begin

1.insert

2.insert//失敗:database is locked

1.commit

2.insert//需要自己主動重新執行

2.commit

完成

可能我默認的鎖的控制級別和他不一樣?也可能我的終端和代碼實現中確切的這些階段其實不對應?

BEGIN [DEFERRED /IMMEDIATE / EXCLUSIVE] TRANSCATION

實測,如果用begin immediate,那麼另一個客戶端連begin都不行了!

如果用begin deferred,可以雙開deferred(注意,兩個終端都用begin deferred,而不是一個加一個不加,這樣實驗更準確),結果和第一個過程一樣!(這樣描述,當第一個事務執行了insert,可能就換成了reserved鎖,佔了reserved鎖,另一個想insert,也需要這個鎖,但是reserved只能有一個,所以得不到,此時第一個需要commit,按他描述的,確實失敗了,原來一個select都影響這麼大,有了select,就佔用了一個shared鎖,不放開,commit就不成功!!!!

 

如果用begin exclusive,那麼其他終端不能begin exclusive,但是能begin(應該是shared),此時,哪怕終端1沒有輸入insert,終端2仍然不能輸入insert(不同於默認或者deferred的“先到先得”——誰先insert,誰先佔坑),屬於先天殘疾。

其實,select都辦不到(本例沒輸入begin;但是先begin;select同樣無效)

 

 

 

 

多種事務混合不同時機和類型:不好確定,很多情況,比如先開普通1,再開exclusive2,那麼普通1仍然不可以insert,先開普通1,但是先insert,只是未commit,exclusive2根本打不開。總之,他還是會遵守大原則,分支情況太多,比較難描述,需要的話還是自己根據自己的需求實際調試一下。——begin immediate好像對應RESERVERD(實測也一樣表現,已經打開的還在,但是不能提交了)

不負責的小結:單機可以用deferred,或者說多個deferred事務開啓後,誰先insert誰先exclusive(也許是pending?pending是鎖的狀態,不是begin帶的選項,pending可以“升級”到exclusive)

代碼中的“事務”和在終端手動顯性“begin”一個事務,不一定是一樣的,也許你執行一個語句,整個流程都封裝在execute內部了。

 

線程模式:

https://www.cnblogs.com/feng9exe/p/10682567.html

單線程模式、多線程模式、串行模式(多進程沒說?這帖子沒提到多進程,但是“採用串行模式,所有線程公用同一個數據庫連接”,指的是多進程麼?

編譯時設定、初始化設定、運行時設定。

事務和插入的區別:每秒能支持50000條數據,但是每秒只能支持幾十個事務,主要是寫磁盤的限制(所以有手動選擇bigin-end時機,wal和stmt之類的吧,當然這些東西場合、時機、效果都不同,不是並列關係)

unlocked、shared、reserved、pending、exclusive,前邊有,不贅述。這些鎖狀態在終端中不一定是顯性指定的,可能你進行了某種操作,自動就變了狀態,我也有演示。

線程的鎖有前置關係?必須先拿低級,再拿高級(也是源碼說明)

鎖狀態的順序問題、一些允許的時機:

**    UNLOCKED -> SHARED

**    SHARED -> RESERVED

**    SHARED -> (PENDING) -> EXCLUSIVE

**    RESERVED -> (PENDING) -> EXCLUSIVE

**    PENDING -> EXCLUSIVE

eFileLock枚舉、

sqlite3OsUnlock解鎖(lower a locking level???),pagerUnlockDb調用前者

此連接推薦了一套設置,在此我就不參考了,每個人的情況不同。

 

 

sqlite中db連接的含義與跨系統多進程

據我瞭解,與mysql不同,sqlite理論上是沒有網絡連接這個概念的,“db”就是直接被“打開”,那麼db是什麼?打開是什麼?db其實就是一個文件,所謂的多個連接,如上,應該是靠系統支持的鎖文件接口來實現互斥的。那麼多個連接對數據庫的寫操作,無論是線程、進程、還是物理主機,我認爲屬於同一種性質(WAL模式除外),無論開幾個終端,開幾個C++進程的open,應該是等價的(可以用來還原情景)。那麼如果這個假設成立,只要用begin immediate,應該就可以實現安全的互斥寫操作。

 

 

stmt

其實不是字符串,也沒這個結構體,本質是Vdbe結構體,裏邊可以預編譯,讓語句執行更快。

reset+bind循環,節省掉finalize和prepare,就快(實測)。

關鍵是,也對應不好stmt和各種時機階段的關係,stmt主要就是prepare、bind、step、reset、finalize,都封裝過了。

stmt沒被很好的初始化(prepare)也可能導致misuse(misuse另一種情況就是sql語句拼錯了)。

stmt的加速機制,不贅述了,就是隻執行一次prepare,只編譯一次,節省了時間,但是我不明白,只編譯一次,怎麼綁進去不同的變量的,變量變了,不需要重新編譯?可能和程序的編譯概念(當然,程序也有運行時的變量和編譯時寫死的變量)不同?可能是某種專門的優化吧。另外,reset到底在循環內下一次bind前,是否需要執行(是!),是否能節省時間(否!)。

實測:如果你想插入不同數據,不reset是根本不行的,貌似並不存在重複bind就能覆蓋前面的效果(內部實現雖然同樣是修改內存,我沒看太仔細,外部看,沒有效果。)。最後,如果不reset,只bind()一次,for loop只有step(),貌似也並沒有快(實測還慢了一秒,從7秒到8秒,從15秒到16秒),所以真的可能是prepare執行的少就能達到加速效果!!!

至於for循環內部也執行prepare,那就呢執行包裹好執行sqlite3_exec是一樣的了

對比:不同方案的併發寫入速度:

sqlite3_exec()10線程乘以500條,46秒

stmt模式 auto increment 表, sqlite3_step()10線程乘以500條,42秒

stmt模式 auto increment 表,不sqlite_reset(重複一樣的數據), sqlite3_step()10線程乘以500條,43秒

stmt模式 非auto increment 表,不sqlite_reset(其實不reset邏輯是錯誤的,至少是不太現實的,也不能靠autoincrement實現主鍵變化,全一樣的數據,只驗證性能用), sqlite3_step()10線程乘以500條,41秒(快是快了,就是沒意義)

stmt模式 非auto increment 表,sqlite_reset(計算id插入), sqlite3_step()10線程乘以500條,42秒(比較現實的方案,確實快了一點)

10*800:非auto increment,非stmt:68秒,stmt:64秒

10*800:auto increment,非stmt:69秒,stmt:67秒,不reset():67

15*1000:stmt:auto:

                                  reset:127,不reset:127

15*5000:stmt:auto:

                                  reset:651,不reset:651

小結:非auto increment更快,stmt更快,不reset基本也不會快(0.排除干擾,只有一次bind,for循環只有step(),並不是“雖然不reset,但還循環bind”的模式1.很難實際應用,2.可能reset和bind本身確實沒什麼開銷,也算驗證了那個prepare纔是編譯過程的“理論”),具體看情況選用方案。所以最現實的最快的是非autoincrement,使用stmt,一個prepare,循環內使用bind和reset。併發的話,沒辦法,必須用autoincrement,不然沒法算id,或者每次插入前開個事務鎖死,並且select count 一下。

 

 

 

鎖的時機——代碼調試用

pagerLockDb()好像是

 

測試經驗

如果只看count來判斷是否丟寫入,要注意加primary key,好像同樣的id輸入兩次也有!!!

多線程(分開的鏈接,獨立open的)有的線程全成功,有的線程全彈21,線程間的不公平不確定什麼導致的(也有全成功,比如慢一些的時候,所以不是外部代碼的問題,是sqlite內部)

 

 

 

 

WAL:

宛如一個小型操作系統內存機構,頁的映射,頁的讀寫與更新,等等,不細說了。總之,利用尾部追加來加速和合並寫操作,但是讀操作因爲要查詢頁表,可能會慢點。實際情況的快慢還要綜合分析。

rollback與wal區別:前者,改寫主db文件,留rollback回滾;後者,改寫緩存wal文件,再整體提交(checkpoint或者backfill)寫入磁盤(db文件)。

這裏有個問題必須確認,併發才能安全,wal的緩存的底層支持問題,NFS和NTFS的區別。

PRAGMA journal_mode=WAL;

併發的致命缺點:訪問數據庫的所有程序必須在同一主機上,且支持共享內存技術。

對應的,delete模式:我後邊實測,確實會慢,從原理上也好解釋,wal是順序寫到wal文件裏,到一個檢查點一起插入;delete是每次插入都先把要改的old-page保存,再修改db文件,是兩次fsync,還包含查找位置的開銷(要寫入的位置離散)。

wal刷寫問題:

我又想了一下,同步節點的問題,思路是一樣的,如果把wal關掉,改爲共享db有問題,那麼不關wal,光是複製,也是有問題(我們的業務下,默認的sqlite配置下)

wal的話,沒做特別設置,那麼複製的db,就不一定是正確的,我寫的demo是這樣的,wal的東西還沒刷進db(默認1000頁才刷),只複製一個db,東西是沒有的,wal快就快在不每次都寫db,而是寫在wal,所以默認肯定不是每次都提交的。

兩個方面解決,一是把wal也複製走,二是加一些手動的沖刷點。

DEMO復現BUG方法:新建一個db,指定爲wal模式,隨便新建表或者插入(別超過1000行,這是默認的checkpoint),然後複製db文件(不包含wal和shm文件)到新路徑,sqlite打開,沒有任何東西,複製wal和shm過來,就發現又有了。如果 先執行刷盤命令再只複製db文件到新路徑,sqlite打開,東西也都在!改爲C++程序寫,stmt模式,帶open和close,帶prepare和finalize,全套,只要不超過1000,同樣只複製db文件,一樣的問題。

關於主動沖刷:命令是pragma wal_checkpoint(FULL),細節下邊連接寫的很好。

https://www.cnblogs.com/cchust/p/4754619.html

不過最後確定了,我自己的demo代碼是多線程的,在主線程丟了一個close db,close db是能保證落盤的,所以如果工程中,所有數據庫操作都有close()作爲節點,那麼下一步只複製db文件也沒錯(如果完整close,應該沒有wal文件了)

 

 

SQLITE_BUSY超時重試

sqlite3_busy_timeout,這個指定的是總時間,重試間隔是內部自動的。

sqlite3_busy_handler 這個自己指定handle處理接口,自己的handle也可以什麼都不做?返回錯誤碼就繼續重試?實測起來略麻煩。

 

 

我遇到的關鍵問題

不加timeout就busy(errcode=5),

加timeout,超時設定很短,彈busy。

加timeout,超時很長,不彈busy,彈出misuse(21)。

timeout居中,可能兩樣都有,可能全通過,多進程高併發21會更多。

21理論上是sql語句錯了(stmt不好看拼好的語句,都封裝了),但是這個21實際上是併發觸發的,代碼本身應該沒問題!!!

一個線程,一錯就全都錯,一個都不成功,和stmt本身有關係嗎?因爲我的循環內,stmt都被reset了,所以原來的stmt不存在,而他的回調和重試需要用到stmt???因爲stmt指針對應的結構位置格式都一樣,好像也不至於崩掉?只是信息和原來不同了?但是也不應該重試就用新的語句吧?所以就失敗了?剛好大家的21都對應的語句錯誤?雖然msg說的還是db lock。

 

 

實測堆棧:

prepare堆棧

用了一個share的鎖

>    sqlite_demo.exe!pagerLockDb(Pager * pPager, int eLock) 行 51983    C
     sqlite_demo.exe!pager_wait_on_lock(Pager * pPager, int locktype) 行 54747    C
     sqlite_demo.exe!sqlite3PagerSharedLock(Pager * pPager) 行 55989    C
     sqlite_demo.exe!lockBtree(BtShared * pBt) 行 66464    C
     sqlite_demo.exe!sqlite3BtreeBeginTrans(Btree * p, int wrflag, int * pSchemaVersion) 行 66839    C
     sqlite_demo.exe!sqlite3InitOne(sqlite3 * db, int iDb, char * * pzErrMsg, unsigned int mFlags) 行 124897    C
     sqlite_demo.exe!sqlite3Init(sqlite3 * db, char * * pzErrMsg) 行 125082    C
     sqlite_demo.exe!sqlite3ReadSchema(Parse * pParse) 行 125108    C
     sqlite_demo.exe!sqlite3LocateTable(Parse * pParse, unsigned int flags, const char * zName, const char * zDbase) 行 108010    C
     sqlite_demo.exe!sqlite3LocateTableItem(Parse * pParse, unsigned int flags, SrcList_item * p) 行 108071    C
     sqlite_demo.exe!sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc) 行 113006    C
     sqlite_demo.exe!sqlite3Insert(Parse * pParse, SrcList * pTabList, Select * pSelect, IdList * pColumn, int onError, Upsert * pUpsert) 行 117972    C
     sqlite_demo.exe!yy_reduce(yyParser * yypParser, unsigned int yyruleno, int yyLookahead, Token yyLookaheadToken, Parse * pParse) 行 153570    C
     sqlite_demo.exe!sqlite3Parser(void * yyp, int yymajor, Token yyminor) 行 154496    C
     sqlite_demo.exe!sqlite3RunParser(Parse * pParse, const char * zSql, char * * pzErrMsg) 行 155636    C
     sqlite_demo.exe!sqlite3Prepare(sqlite3 * db, const char * zSql, int nBytes, unsigned int prepFlags, Vdbe * pReprepare, sqlite3_stmt * * ppStmt, const char * * pzTail) 行 125299    C
 

pagerLockDb

NT系統用sqlite封裝的osLockFileEx(),否則用osLockFile()

step堆棧

觀察到locktype,先SHARED_LOCK     1,後RESERVED_LOCK   2

newLocktype = RESERVED_LOCK;

sqlite3VdbeExec過程比較多,最後還會變成EXCLUSIVE_LOCK 4

>    sqlite_demo.exe!winLockFile(void * * phFile, unsigned long flags, unsigned long offsetLow, unsigned long offsetHigh, unsigned long numBytesLow, unsigned long numBytesHigh) 行 43322    C
     sqlite_demo.exe!winLock(sqlite3_file * id, int locktype) 行 44108    C
     sqlite_demo.exe!sqlite3OsLock(sqlite3_file * id, int lockType) 行 22438    C
     sqlite_demo.exe!pagerLockDb(Pager * pPager, int eLock) 行 51981    C
     sqlite_demo.exe!sqlite3PagerBegin(Pager * pPager, int exFlag, int subjInMemory) 行 56656    C
     sqlite_demo.exe!sqlite3BtreeBeginTrans(Btree * p, int wrflag, int * pSchemaVersion) 行 66845    C
     sqlite_demo.exe!sqlite3VdbeExec(Vdbe * p) 行 87391    C
     sqlite_demo.exe!sqlite3Step(Vdbe * p) 行 82300    C
     sqlite_demo.exe!sqlite3_step(sqlite3_stmt * pStmt) 行 82366    C
     sqlite_demo.exe!insert_busy_handle_child_thread(parameters p) 行 284    C++
 

>    sqlite_demo.exe!sqlite3OsLock(sqlite3_file * id, int lockType) 行 22439    C
     sqlite_demo.exe!pagerLockDb(Pager * pPager, int eLock) 行 51981    C
     sqlite_demo.exe!pager_wait_on_lock(Pager * pPager, int locktype) 行 54747    C
     sqlite_demo.exe!sqlite3PagerExclusiveLock(Pager * pPager) 行 57133    C
     sqlite_demo.exe!vdbeCommit(sqlite3 * db, Vdbe * p) 行 79065    C
     sqlite_demo.exe!sqlite3VdbeHalt(Vdbe * p) 行 79497    C
     sqlite_demo.exe!sqlite3VdbeExec(Vdbe * p) 行 85027    C
     sqlite_demo.exe!sqlite3Step(Vdbe * p) 行 82300    C
     sqlite_demo.exe!sqlite3_step(sqlite3_stmt * pStmt) 行 82366    C
     sqlite_demo.exe!insert_busy_handle_child_thread(parameters p) 行 284    C++
 

reset

主要看一下能不能跟蹤到對stmt的改變,是不是一位內reset,busy handler收到了影響。

sqlite3VdbeReset,太亂了。。。。。跟不了。。。

 

所有misuse打斷點

跟蹤問題:改成單線程,逐步跟蹤流程與機制

既然是misuse,sqlite3MisuseError

#define SQLITE_MISUSE_BKPT sqlite3MisuseError(__LINE__)

說明文檔對這些的描述,就是方便你打斷點。。。

根據log,我肯定不是這裏

 

多線程插入

timeout設置很小(10)時,實際是vdbeUnbind報錯,而且是循環的第一次,還沒有reset過,只prepare,然後就是bind

再跑兩次,符合預期,因爲超時很小,都是返回busy。

 

static int sqliteDefaultBusyCallback()有算法,粗看,前期好像是按一個隊列逐漸增加等待,後期超時直接返回0(其實就是不管了)

(另一種回調sqlite3_busy_handler(),其實就是替換的這個接口,返回0成功,返回1重試,看來還是用timeout好一些)

 

關於sqliteDefaultBusyCallback的堆棧

     sqlite_demo.exe!sqliteDefaultBusyCallback(void * ptr, int count, sqlite3_file * pFile) 行 157790    C
>    sqlite_demo.exe!sqlite3InvokeBusyHandler(BusyHandler * p, sqlite3_file * pFile) 行 157822    C
     sqlite_demo.exe!btreeInvokeBusyHandler(void * pArg) 行 65701    C
     sqlite_demo.exe!pager_wait_on_lock(Pager * pPager, int locktype) 行 54748    C
     sqlite_demo.exe!sqlite3PagerSharedLock(Pager * pPager) 行 55989    C
     sqlite_demo.exe!lockBtree(BtShared * pBt) 行 66464    C
     sqlite_demo.exe!sqlite3BtreeBeginTrans(Btree * p, int wrflag, int * pSchemaVersion) 行 66839    C
     sqlite_demo.exe!sqlite3VdbeExec(Vdbe * p) 行 87391    C
     sqlite_demo.exe!sqlite3Step(Vdbe * p) 行 82300    C
     sqlite_demo.exe!sqlite3_step(sqlite3_stmt * pStmt) 行 82366    C
     sqlite_demo.exe!insert_busy_handle_child_thread(parameters p) 行 288    C++
 

timeout設置1000,同樣出現busy,最終插入數量應該也是不全。會丟不少

timeout設置5000,busy少了很多(我感覺自動重試間隔和你設置的超時時間是有比例的),還是丟,24*500=12000,實際11913,接近了

timeout設置60*1000,超級多的misuse database is locked,實際寫入更小了!,費解!

設置小了,超時就不寫入了,設置大了,更寫不進去,詭異。甚至說,同樣的設定,用VS調試就能插入,用VS運行就不行!
 

最後雙開進程,每個都開14個線程,有一個進程是大規模的報錯21,misuse,db lock,但是後期是重試了,最終24000個數據(好像不對,我雙開14線程,應該28000,應該還是丟了4個線程的數據)

可能超時設置的不夠,進程運行超了1分鐘,不保險,設10分鐘試試

實測還是會有丟,猜測回調不具有存活能力,讓線程多活一會,sleep一下。sleep什麼用都沒有,在close連接之前sleep也沒用,經過斷點觀察,猜測是多線程,有的prepare失敗了,所以整個線程都沒插入,而我沒給prepare設置handle,只是在循環前邊設置了handle。(初代demo死活想不到prepare還會失敗的,還好多打了斷點)

更早的使用timeout函數,現在單機多線程多進程插入都成功了!

 

demo後續

首先改成網絡版本,改路徑,遠程打開數據庫,測試多機共同寫入。

關於db名的拼寫,一直打不開,對比一下現有工程,或者跟蹤一下報錯信息。

然後,對比一下工程用的sqlite3_open_v2()和默認用的sqlite3_open()的區別。

最後,把原工程加上timeout測試實際效果。

 

對原分佈式工程進行修改

前邊該試驗的都試驗了,該對我本來的工程進行修改了,實際上重寫機制是沒多大問題,問題出在速度上。在使用DELETE模式替換WAL模式後,有些寫入步驟幾乎沒有差別,但是有個別階段,時間明顯被延長了。當然,這也需要我再細化一下具體工程邏輯去定位慢在哪,但是說明delete把wal確實慢。

這就難了,wal不保險,delete慢,只能兩個角度看,代碼能不能優化?wal能否改的更保險(比如不同階段的同步控制,通過主動checkpoint來避免分佈式有不同步問題)

wal的可調整項:wal_autocheckpoint=1000和journal_size_limit=1000,好像是不怎麼靈活,是一次性的設定?而不是手動提交,這樣應該大了還是不能控制節點,小了一樣會很慢。

理清思路以後,看了一下現階段的分佈式方案:前三步寫入db的操作只能有一個進程操作,後邊多進程併發是複製db到本地,要想達到不復制db到本地(佔用大量空間)的目的,共享一個主機db,確實按現階段流程是可以控制的,一些關鍵節點主動用wal checkpoint就行了,這樣就能繼續用wal模式保證速度,同時節省空間。

但是長久看,爲了加速,所有過程都可能要做分佈式,一旦要併發寫入,那麼跨主機肯定不能wal了,而且NFS下,是否wal,sqlite都不太穩妥,所以臨時方案也先不做了,以後遷移mysql(還要確定大數據量下,幾十個機器共同訪問一個mysql是否比現在複製db到本地的方案更快)。

重點:我們是用分佈式加速一個程序的一整套流水過程,不是專門做服務器,所以很多方面和業務細節需要考慮,如果一個方案,看起來很穩,但是實測不加速,可能減速,這個方案就不能用。

todo:不管用不用sqlite做分佈式,這兩個問題我還是可以再測試一下,1.手動沖刷wal日誌的操作,2.多主機併發wal的後果。

 

 

 

 

 

 

 

----------------------------

-----------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

其他

根據我之前的經驗,我們的多進程會發生busy的問題,並且busy的後果是操作失敗。以前以爲是trans粒度大導致互斥,如果代碼中transaction並不是顯性immediate的,那麼改trans粒度並不加快或者減慢速度,而且也仍然治不好busy的問題。

 

我要處理的工程裏邊有些讀過程出現了鎖,這個很奇怪,但是實際代碼就如上圖,是默認的begin,也就是deferred模式,內部也沒有寫操作,按理說雖然顯式地begin一個事務,但是事務沒有升級鎖狀態,不應該產生一個獨佔區間。目前沒看到有資料顯示可以提前設置默認的begin模式,以至於begin不是deferred模式。有嗎?

 


 

使用autoincrement,不指定primary key,timeout是否會導致插入過多?


早期代碼寫錯,確實遇到過,那時候有的線程不工作(prepare失敗),有的線程可能重複插入了,當時通過指定primary key避免了,現在模擬實際情況,是不指定id的自由插入,primary key+auto increment,實測,暫時還沒發現多插入的現象。

create table if not exists t1 (id integer primary key autoincrement, x integer , y integer, weight real)

如何不指定主鍵?我暫時是這樣做的,也不知道bind null算是綁了0還是-1(實際手寫這兩個值都不行),總之能保證正確插入,

INSERT INTO t1 (id,x, y, weight) VALUES(?,?,?,?);
sqlite3_bind_null(stmt, nCol++);

 哦,原來就是null

INSERT INTO t1 (id,x, y, weight) VALUES(null,1,1,1);

 

自動優化

實測,for循環bind-step-reset,不結束不finalize,2進程*8線程*1000個循環==16000次插入,他還是大概會每個線程連續插入500次(不絕對,也有的過程十來次)

當然,這個過程怎麼解釋,也保留只是線程按時間片來回切換的可能性,而與sqlite的優化無關。

第一列是自增主鍵,第二列是每個線程自己覺得自己應該是什麼id(不自增的時候用來讓併發進程線程覆蓋所有id用的,現在用來觀察併發過程)

 

 

sqlite3_exec()與stmt模式下sqlite3_step()、sqlite3_finalize()等接口的關係,其實exec是這幾個函數的打包封裝接口。

 

 

select 多個返回

需要多次執行sqlite3_step(),每次SQLITE_ROW,直到SQLITE_DONE

 

 

 

附錄:其實網上所有帖子基本都是官方說明的翻譯,還有源碼註釋

比較詳細的wal過程:但是圖缺失

https://www.2cto.com/database/201604/497268.html

 

官方的原子提交的說明:文章右側有配圖,也比較全,但是內容比較長,英文的。

https://www.sqlite.org/atomiccommit.html

 

官方多線程說明:

https://www.sqlite.org/threadsafe.html

 

官方FAQ:

https://www.sqlite.org/faq.html#q19

第五個問題,是我關心的:多應用,多例程,訪問同一個db文件。

描述的情形和多線程也差不多,可以同時打開同時查詢,只有一個能改寫。

reader/writer鎖控制db的訪問。(win Me以前的功能差一些)NFS可能支持不太好(針對的不只是win me)?!!!!

fcntl() 文件鎖在NFS可能是壞的!

多進程訪問sqlite,db文件不應該在NFS系統上。

微軟文檔說明:沒有Share.exe的話,FAT的locking都可能失靈。

工程人員的經驗之談:網絡文件的file locking特別不可靠,bug多。(但是這裏作者用了if,)

剩下就是一些作者的絮叨,比如大併發推薦你用mysql之類的(他們的先天優勢:網絡通信,只有單機單進程訪問數據庫,安全可控),但是其實你的實際需求遠比你想象的小之類的(還是忽悠你用sqlite)。。

 

sqlite原則上是支持多進程併發,就是NFS文件系統坑了!!

但是我又想了一下,現在的windows主流是NTFS,是否能等同NFS的缺陷?不確定,NTFS+fcntl()這方面資料貌似也不多,還需要查證,但是這是最關鍵的一點,如果NTFS和NFS一樣,那麼跨進程跨系統就廢了。

至少目前我表面遇到的現象倒是還正常,我遇到SQLite_BUSY說明他的鎖暫時還是起作用的,可靠不可靠就不清楚了。全看NTFS。

補充:有些歧義:NFS是特指linux的NFS協議嗎?還是所有?windows好像是SMB,作者專門提到windowsFAT有問題,其他的,好像也不行(network files還是不行,作者還說了個if,真坑):

People who have a lot of experience with Windows tell me that file locking of network files is very buggy and is not dependable. If what they say is true, sharing an SQLite database between two or more Windows machines might cause unexpected problems.

 

 

WAL性能測試,有一些詳細對比,可能參考的上。

https://www.cnblogs.com/cchust/p/4754580.html

 

 

一些編譯問題,我感覺問題不大,反正就是頭文件和源文件放好,就能編譯成功

http://www.360doc.com/content/14/0416/19/8425146_369537570.shtml

 

底層機制:NFS+RPC/SMB+fcntl()

https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/9719420?fromtitle=NFS&fromid=812203&fr=aladdin

 

有人說網絡鄰居是smbhttps://zhidao.baidu.com/question/36003005.html

不過我也遇到一個情況,網絡鄰居是共享了,但是sqlite卻沒能跨主機訪問目標db(ip地址+目錄,同樣地址在資源瀏覽器可以打開,不過很奇怪的是,我們的大的分佈式工程,同樣的地址格式,就能跨主機訪問)

我現在也不確定當作者提到NFS,到底是專指linux/unix的協議NFS,還是指網絡文件系統的概念(包括SMB)。

 

 

 

附錄:sqlite和mysql的對比

sqlite優勢:磁盤效率、小型化,也可多終端打開,多進程(多機就不行了)

sqlite劣勢:無用戶管理,無法跨主機併發訪問(可以,但是不可靠,不可預知結果)

mysql優勢:有用戶管理,單進程管理數據庫,不跨主機(其他的進程通過網絡接口訪問,相當於mysql有一個專門的守護程序,而sqlite缺這麼一個機制),安全,表級互斥(前者庫級互斥)。比較全面、綜合,適合併發,其他一些細節就不說了,什麼安裝容易、三方庫、可視化。

mysql:某些場景某些操作,可能沒有sqlite那麼快(當然,這個還是要實測才能確定),和標準SQL的語法兼容性(次要),mysql服務器要佔用硬盤空間安裝(相比sqlite遇到的問題和實際數據量,這幾百兆接受)。

 

轉mysql的必要理由:sqlite在NFS下不可靠,很好理解,鎖是系統內的機制,跨系統的話,就要看文件系統的支持了,如果不行,明顯是沒法互斥的。

 

何止數據庫

我自己的這個工程,他本身的“分佈式”做的很簡陋,如果sqlite的NFS文件鎖不可靠,那麼這個工程的分佈式基礎——文件鎖——就不可靠。

 

 

 

 

 

 

發佈了183 篇原創文章 · 獲贊 376 · 訪問量 116萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章