樹(shù)莓派開(kāi)發(fā)筆記(十七):樹(shù)莓派4B+上Qt多用戶連接操作Mysql數(shù)據(jù)庫(kù)同步(單條數(shù)據(jù)悲觀
前言
??安裝了mysq數(shù)據(jù)庫(kù),最終時(shí)為了實(shí)現(xiàn)在一個(gè)樹(shù)莓派上實(shí)現(xiàn)多用戶多進(jìn)程操作的同步問(wèn)題,避免數(shù)據(jù)并發(fā)出現(xiàn)一些錯(cuò)誤,本篇安裝了遠(yuǎn)程服務(wù)并且講述了使用Qt進(jìn)行悲觀鎖for update操作,命令行進(jìn)行同步查詢的示例。
其他操作
??這里只是稍微提一下,具體參照博主的樹(shù)莓派系列博客,非常詳細(xì)。
??遠(yuǎn)程登陸界面
sudo apt-get install tightvncserversudo apt-get install xrdpsudo service xrdp restartsudo ufw allow 3389sudo service ufw restart
??然后可以使用window遠(yuǎn)程桌面登陸了:
??默認(rèn)用戶名:pi
??默認(rèn)密碼:raspberry
??

??

安裝qt5
sudo apt-get install qt5-defaultsudo apt-get install qtcreator
??安裝好后,遠(yuǎn)程桌面的程序里面就多了個(gè)qtcreator了:
??

??
??創(chuàng)建一個(gè)界面工程,然后運(yùn)行:
??(編譯速度比幾年前的3B+快一些,后續(xù)開(kāi)發(fā)過(guò)程中測(cè)試一下,是否可以忽略3B+的交叉編譯)
??

??檢查數(shù)據(jù)庫(kù)驅(qū)動(dòng):
??

??沒(méi)有mysql的數(shù)據(jù)庫(kù)驅(qū)動(dòng)。
sudo apt-get install libqt5sql5-mysql
??

Qt操作mariadb數(shù)據(jù)庫(kù)
QSqlDatabase db;db = QSqlDatabase::addDatabase("QMYSQL");db.setHostName("127.0.0.1");db.setPort(3306);db.setDatabaseName("data");db.setUserName("root");db.setPassword("a1234567");if(db.open()){
? ?LOG << "Succeed to open db";}else{
? ?LOG << "Failed to open db:" << db.lastError().text();
? ?return;}QString cmd = "select * from student;";QSqlQuery query = db.exec(cmd);while(query.next()){
? ?LOG << query.value(0).toString()
? ? ? ?<< query.value(1).toString()
? ? ? ?<< query.value(2).toString()
? ? ? ?<< query.value(3).toString();}
??

多用戶操作
??本意是為了多用戶操作,那么讀的時(shí)候需要加讀鎖,寫(xiě)的時(shí)候需要加寫(xiě)鎖。
??兩個(gè)用戶同時(shí)讀取了數(shù)據(jù)庫(kù)中的一條記錄,此時(shí)用戶A對(duì)其中一個(gè)字段的值進(jìn)行了修改操作并進(jìn)行了提交,后來(lái)用戶B也對(duì)這個(gè)字段進(jìn)行了修改,用戶B的提交將會(huì)覆蓋用戶A提交的值。
??

鎖類(lèi)型
悲觀鎖
??每次去取數(shù)據(jù),很悲觀,都覺(jué)得會(huì)被別人修改,所以在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖。簡(jiǎn)言之,共享資源每次都只給一個(gè)線程使用,其他線程阻塞,等第一個(gè)線程用完后再把資源轉(zhuǎn)讓給其他線程。synchronized和ReentranLock等都是悲觀鎖思想的體現(xiàn)。
樂(lè)觀鎖
??每次去取數(shù)據(jù),都很樂(lè)觀,覺(jué)得不會(huì)被被人修改。因此每次都不上鎖,但是在更新的時(shí)候,就會(huì)看別人有沒(méi)有在這期間去更新這個(gè)數(shù)據(jù),如果有更新就重新獲取,再進(jìn)行判斷,一直循環(huán),直到拿到?jīng)]有被修改過(guò)的數(shù)據(jù)。(mysql需要自己實(shí)現(xiàn)樂(lè)觀鎖)。
for update使用場(chǎng)景(悲觀鎖)
??for update 可以為數(shù)據(jù)庫(kù)中的一行數(shù)據(jù)加上一個(gè)排它鎖。當(dāng)一個(gè)事務(wù)的操作未完成時(shí)候,其他事務(wù)可以讀取但是不能寫(xiě)入或更新。
??如果項(xiàng)目對(duì)某個(gè)數(shù)據(jù)準(zhǔn)確性有要求,并且項(xiàng)目存在并發(fā)(不一定高并發(fā)),則需要使用 for update。
??比如:用戶A使用余額購(gòu)買(mǎi)商品,此時(shí)用戶B向用戶A發(fā)起轉(zhuǎn)賬,如果恰好處在同一時(shí)間,則可能造成用戶A最終余額錯(cuò)誤。此時(shí)需要使用 for update 進(jìn)行數(shù)據(jù)加鎖防止出錯(cuò)。
??這種情況下,即使并發(fā)很小,但是也會(huì)有一定的概率會(huì)碰到,而余額的錯(cuò)誤即使差一分錢(qián)也是不能容忍的,所以這種特定的場(chǎng)景,即使不是高并發(fā),也應(yīng)該使用 for update 規(guī)避問(wèn)題。
for update 用法
begin;select * from XXX where XXX for update;...commit;
??for update 必須在事務(wù)中才生效。
Qt測(cè)試
使用127.0.0.1的ip進(jìn)行連接(本地連接)
QSqlDatabase db;db = QSqlDatabase::addDatabase("QMYSQL");db.setHostName("127.0.0.1");db.setPort(3306);db.setDatabaseName("data");db.setUserName("root");db.setPassword("a1234567");if(db.open()){
? ?LOG << "Succeed to open db";}else{
? ?LOG << "Failed to open db:" << db.lastError().text();
? ?return;}if(db.transaction()){
? ?QString cmd = "select * from student for update;";
? ?QSqlQuery query = db.exec(cmd);
? ?while(query.next())
? ?{
? ? ? ?LOG << query.value(0).toString()
? ? ? ? ? ?<< query.value(1).toString()
? ? ? ? ? ?<< query.value(2).toString()
? ? ? ? ? ?<< query.value(3).toString();
? ?}
? ?for(int index = 0; index < 10; index++)
? ?{
? ? ? ?QThread::sleep(1);
? ? ? ?LOG << "sleep:" << index;
? ?}
? ?if(!db.commit())
? ?{
? ? ? ?LOG << "Failed to commit";
? ?}}
???

??至此,我們的鎖加入成功,說(shuō)清楚原理可以方便大家著手開(kāi)始開(kāi)發(fā)多用戶進(jìn)程操作數(shù)據(jù)庫(kù)的同步開(kāi)發(fā)了。
使用局域網(wǎng)的ip進(jìn)行連接(遠(yuǎn)程連接)
QSqlDatabase db;db = QSqlDatabase::addDatabase("QMYSQL");db.setHostName("192.168.0.103");db.setPort(3306);db.setDatabaseName("data");db.setUserName("root");db.setPassword("a1234567");if(db.open()){
? ?LOG << "Succeed to open db";}else{
? ?LOG << "Failed to open db:" << db.lastError().text();
? ?return;}if(db.transaction()){
? ?QString cmd = "select * from student for update;";
? ?QSqlQuery query = db.exec(cmd);
? ?while(query.next())
? ?{
? ? ? ?LOG << query.value(0).toString()
? ? ? ? ? ?<< query.value(1).toString()
? ? ? ? ? ?<< query.value(2).toString()
? ? ? ? ? ?<< query.value(3).toString();
? ?}
? ?for(int index = 0; index < 10; index++)
? ?{
? ? ? ?QThread::sleep(1);
? ? ? ?LOG << "sleep:" << index;
? ?}
? ?if(!db.commit())
? ?{
? ? ? ?LOG << "Failed to commit";
? ?}}
??連接不上:
??

??這個(gè)時(shí)候需要修改下配置:
sudo vi /etc/mysql/mariadb.conf.d/50-server.cnf
??

??重啟服務(wù):
systemctl restart mysql.service
??

??繼續(xù)測(cè)試,下面是用樹(shù)莓派使用局域網(wǎng)ip(之前也不能連接127.0.0.1):
??

??連接成功,然后在局域網(wǎng)pc上連接,但是不能打開(kāi)事務(wù):
??

??修改一下:
??

??這個(gè)其實(shí)是跟mysql驅(qū)動(dòng)有關(guān)系,因?yàn)楣P者是幾年前弄得libmysql.dll和libmysqld.dll,這個(gè)不深究了,已經(jīng)可以遠(yuǎn)程操作了。