前言
安装了mysq数据库,最终时为了实现在一个树莓派上实现多用户多进程操作的同步问题,避免数据并发出现一些错误,本篇安装了远程服务并且讲述了使用Qt进行悲观锁for update操作,命令行进行同步查询的示例。
其他操作
这里只是稍微提一下,具体参照博主的树莓派系列博客,非常详细。
远程登陆界面
sudo apt-get install tightvncserver sudo apt-get install xrdp sudo service xrdp restart sudo ufw allow 3389 sudo service ufw restart
然后可以使用window远程桌面登陆了:
默认用户名:pi
默认密码:raspberry
安装qt5
sudo apt-get install qt5-default sudo apt-get install qtcreator
安装好后,远程桌面的程序里面就多了个qtcreator了:
创建一个界面工程,然后运行:
(编译速度比几年前的3B+快一些,后续开发过程中测试一下,是否可以忽略3B+的交叉编译)
检查数据库驱动:
没有mysql的数据库驱动。
sudo apt-get install libqt5sql5-mysql
Qt操作mariadb数据库
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(); }
多用户操作
本意是为了多用户操作,那么读的时候需要加读锁,写的时候需要加写锁。
两个用户同时读取了数据库中的一条记录,此时用户A对其中一个字段的值进行了修改操作并进行了提交,后来用户B也对这个字段进行了修改,用户B的提交将会覆盖用户A提交的值。
锁类型
悲观锁
每次去取数据,很悲观,都觉得会被别人修改,所以在拿数据的时候都会上锁。简言之,共享资源每次都只给一个线程使用,其他线程阻塞,等第一个线程用完后再把资源转让给其他线程。synchronized和ReentranLock等都是悲观锁思想的体现。
乐观锁
每次去取数据,都很乐观,觉得不会被被人修改。因此每次都不上锁,但是在更新的时候,就会看别人有没有在这期间去更新这个数据,如果有更新就重新获取,再进行判断,一直循环,直到拿到没有被修改过的数据。(mysql需要自己实现乐观锁)。
for update使用场景(悲观锁)
for update 可以为数据库中的一行数据加上一个排它锁。当一个事务的操作未完成时候,其他事务可以读取但是不能写入或更新。
如果项目对某个数据准确性有要求,并且项目存在并发(不一定高并发),则需要使用 for update。
比如:用户A使用余额购买商品,此时用户B向用户A发起转账,如果恰好处在同一时间,则可能造成用户A最终余额错误。此时需要使用 for update 进行数据加锁防止出错。
这种情况下,即使并发很小,但是也会有一定的概率会碰到,而余额的错误即使差一分钱也是不能容忍的,所以这种特定的场景,即使不是高并发,也应该使用 for update 规避问题。
for update 用法
begin; select * from XXX where XXX for update; ... commit;
for update 必须在事务中才生效。
Qt测试
使用127.0.0.1的ip进行连接(本地连接)
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"; } }
至此,我们的锁加入成功,说清楚原理可以方便大家着手开始开发多用户进程操作数据库的同步开发了。
使用局域网的ip进行连接(远程连接)
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"; } }
连接不上:
这个时候需要修改下配置:
sudo vi /etc/mysql/mariadb.conf.d/50-server.cnf
重启服务:
systemctl restart mysql.service
继续测试,下面是用树莓派使用局域网ip(之前也不能连接127.0.0.1):
连接成功,然后在局域网pc上连接,但是不能打开事务:
修改一下:
这个其实是跟mysql驱动有关系,因为笔者是几年前弄得libmysql.dll和libmysqld.dll,这个不深究了,已经可以远程操作了。