CodeIgniter框架数据库事务处理的设计缺陷及解决方案
原因:
在我们这一行的一个业务中,使用的是旧版的CodeIgniter框架,DB类中的DB事务处理存在设计缺陷,可能不认为是缺陷。但它影响我们的生产环境,导致连锁反应。对业务影响很大,不容易查。这个问题我在今年3月中旬跟codeigniter中国的站长Hex汇报过,然后就忘了。直到今天,我们的网商再一次想到了这个问题,这让我又检查了一遍。具体原因请听我慢慢说完。(此问题在最新版本2.1.0中也存在)
分析:
以CodeIgniter框架2.1.0版本为例,系统\数据库\ DB _ driver.php的CI_DB_driver类第58行有一个$_trans_status属性。
复制代码如下://system \ database \ db _ driver . phpvar $ trans _ strict=true;var $ _ trans _ depth=0;var $_trans_status=真;//与事务一起使用,以确定是否应该进行回滚$ cache _ on=FALSE
同时,在这个类的查询方法中,有一个分配这个属性的代码。请参见文件第306行和第307行中的复制代码,如下所示://如果正在使用事务,这将触发回滚$ this-_ trans _ status=false;
这里还给出了一个注释,告诉我们如果使用事务处理,这个属性将成为回滚的决定性条件。
第520行交易提交方式trans_complete,代码如下:复制代码如下:/* * * complete transaction * * @ accesspublic * @ return bool */functiontrans _ complete(){ if(!$ this-trans _ enabled){返回FALSE}
//当事务嵌套时,我们只开始/提交/回滚最外层的事务if($ this-_ trans _ depth 1){ $ this-_ trans _ depth-=1;返回真;}
//如果查询失败,query()函数将此标志设置为FALSE($ this-_ trans _ status===FALSE){ $ this-trans _ roll back();
//如果我们不以严格模式运行,我们将重置//trans _ status标志,以便允许后续的事务组//,If($ this-trans _ strict===FALSE){ $ this-_ trans _ status=TRUE;}
log_message(“调试”、“数据库事务失败”);返回FALSE}
$ this-trans _ commit();返回真;}
在第535行,如果_trans_status属性为假,将发生回滚并返回假。
在我们的业务代码中,由于程序员的疏忽,我们没有判断trans_complete()方法是否执行正确,直接告诉用户操作成功。然而,实际上,程序已经给了数据库一个回滚指令,并且未能更新数据库记录。当用户执行下一个操作时,程序发现相应的记录没有更新,并提醒用户上一个操作没有完成,并通知用户再次执行。如此反复.
故障排除过程也非常有趣。一开始从PHP代码来看,问题总是不确定的,重点也没有放在trans_complete()方法的返回上。直到strace捕获了包分析,才知道这个属性导致了回滚。
复制代码代码如下:22:54:08.380085写(9,' _ \ 0 \ 0 \ 3更新` CFC 4n _ user _ info ` SET ` CFC 4n _ user _ lock `=1 \ n这里` CFC 4n _ user _ id `=\ ' 6154 \ ' \ AnD ` CFC 4n _ user _ lock `=0 ',99)=99 //执行更新命令22:54:08.380089读取(9,' : \ 0 \ 0 \ 1 \ 377 \ 36 \ 4 #未知列CFC 4n _ user _ lock ' in '在哪里子句\ ',16384)=62 //不存在字段,SQL执行错误22:54:08.381791写入(9,' \ 21 \ 0 \ 0 \ 0 \ 3设置自动提交=0 ',21)=21 //禁止自动提交22:54:08.381891 read(9,' \7\0\0\1\0\0\0\0\0 ',16384)=1122:54:08.382186 poll([{ FD=9,events=POLLIN|POLLPRI}],1,0)=022336054:08 .开始事务处理22:54:08.401954写入(9,' \v\0\0\0\2database_demo ',15)=1522:543:08 . 4:08 . 4336008 . 4336008 . 4336008读取(9,' \7\0\0\1\0\0\0\1\0\1\0 ', 16384)=1122:54:08.417773写入(9,' \v\0\0\0\2database_demo ',15)=1522:543:08 . 4336008 . 4336008 . 4336008 . 4336008 . 4336008读取(9,' \ 7 \ 0 \ 0 \ 0 \ 1 \ 0 \ 0 \ 1 \ 0 \ 0 ',16384)=11223:54333执行其他结构化查询语言语句22:54:08.418363 read(9,' 0 \ 0 \ 0 \ 0 \ 1 \ 0 \ 1 \ 0 \ 1 \ 0 \ 0 \ 0(行匹配: 1已更改: 1警告: 0 0 ',16384)=52 //成功更新,影响条数1.22:54:08.430212写入(9,' \v\0\0\0\2database_demo ',15)=15223:08 . 430314 . 43031403114读取(9,' \ 7 \ 0 \ 0 \ 0 \ 1 \ 0 \ 0 \ 0 \ 0 ',16384)=1122:54336008。执行其他SQK语句22:54:08.430814读取(9,' 0\0\0\1\0\1\0\1\0\0\0(行匹配: 1更改: 1警告: 0 0 ', 16384)=52 //成功更新,影响条数1.22:54:08.432130写入(9,' \v\0\0\0\2database_demo ',15)=15223:08 . 4323600008读取(9,' \ 7 \ 0 \ 0 \ 0 \ 1 \ 0 \ 0 \ 0 \ 1 \ 0 \ 0 ',16384)=1122336054336008。执行其他SQK语句22:54:08.432743读取(9,' 0 \ 0 \ 0 \ 0 \ 1 \ 0 \ 1 \ 0 \ 1 \ 0 \ 0 \ 0(行匹配: 1已更改: 1警告: 0 0 ',16384)=52 //成功更新,影响条数1.22:54:08.433517写入(9,' \v\0\0\0\2database_demo ',15)=1522:08。 4336008 .4336008读取(9,' \ 7 \ 0 \ 0 \ 0 \ 1 \ 0 \ 0 \ 0 \ 1 \ 0 \ 0 ',16384)=1122:54336008。回滚事务#注意看这里22:54:08.434041读(9,' \7\0\0\1\0\0\0\0\0\0 ',16384)=1122:54:08 .434914写(9,' \v\0\0\0\2database_demo ',15)=1522:543:8。434344913608 \7\0\0\1\0\0\0\0\0\0\0 ',16384)=1122:54336008.435342写入(9,' \ 21 \ 0 \ 0 \ 0 \ 3设置自动提交=1 ',21)=21 //恢复自动提交22:54:08.435430读取(9,' \ 7 \ 0 \ 0 \ 1 \ 0 \ 0 \ 2 \ 0 \ 0 \ 0 ',16384)=1122:54:08.436923写入(9,' \1\0\0\0\1 ',5)=5
可以看到,在22:54:08.380085时间点处,发送更新结构化查询语言语句指令,在22:54:08.380089时间读取返回结果,得到结构化查询语言执行错误,不存在字段”CFC 4n _ user _ lock”;22:54:08.381791和22:54:08.382703两个时间点,PHP发送停止"自动提交"与"开始事务处理"指令,在22:54:08.433954 发送"事务回滚"指令。
配合如上的代码分析,可以清楚的知道,因为" UPDATE ` CFC 4n _ user _ info ` SET ` CFC 4n _ user _ lock `=1 WHERE ` CFC 4n _ user _ id `=' 6154 '和` cfc4n_user_lock`=0 "这句结构化查询语言执行错误,导致$ _交易状态属性被设置为假的,当代码提交事务时,被传输完成()方法判断,认为"上一个事务处理"(下面将仔细分析)中存在结构化查询语言语句执行失败,决定回滚事务,不提交。
有些朋友可能不理解刚才提到的“最后一笔交易”。让我们回到代码,看看这个属性。同样在trans_complete方法中,第542-545行:
复制代码如下://如果我们不是在严格模式下运行,我们将重置//的_trans_status标志,以便允许后续的事务组//,if($ this-trans _ strict===FALSE){ $ this-_ trans _ status=TRUE;}
从注解中也很容易理解,设定CI的设计者,为了更严谨地处理同一个脚本,当有多个交易时,交易之间的关系很重要,所有的交易都是光荣的,但所有的损失都是损失。这里的trans_strict属性是一个开关。trans_strict为false时,为非严格模式,表示多个事务之间的关系不重要,互不影响。当前事务中有一条SQL语句执行失败,不能影响自身。将状态设置为真。毫无疑问,这是一个非常彻底的考虑。考虑多个事务之间的关系是为了确保业务在更严格的代码上运行。
然而,在我们的代码中,错误的SQL语句是在事务外部执行的,而不是在事务内部。根据我们对事务的理解,我们可以清楚地知道,外部事务的SQL比内部事务的SQL更重要,外部事务可以允许错误,但必须是内部事务正确的,不受外部干扰。但是在CI的框架下,因为事务以外的一条语句执行失败,整个事务回滚了……当然,我们的程序员没有判断事务提交方式的返回,这也是一个问题。
问题很清楚,所以解决方案对你来说一定很简单。例如,在trans_start方法中,为_trans_status属性分配一个值,并将其设置为TRUE,忽略事务之外的问题。
复制代码如下:函数trans _ start($ test _ mode=false){ if($ this-trans _ strict==false){ $ this-_ trans _ status=true;//在事务处理开始时将该属性的值重置为true }//2012/05/01 18:00被CI中文社区的朋友http://codeigniter.org.cn/forums/space-uid-5721.html,更正。修改为增加trans_strict属性进行判断,最好决定是否重置_trans_status。if(!$ this-trans _ enabled){返回FALSE}
//当事务嵌套时,我们只开始/提交/回滚最外层的事务if($ this-_ trans _ depth 0){ $ this-_ trans _ depth=1;返回;}
$ this-trans _ begin($ test _ mode);}
结束:
如果不理解对方的设计意图,就不能盲目定义对方的代码评价,不管程序作者的水平如何。比自己强,不能盲目崇拜;比自己弱,更别说指责;了解设计意图,学习别人优秀的设计思想、代码风格和算法效率,是一个好习惯。当然,codeigniter框架非常优秀。
版权声明:CodeIgniter框架数据库事务处理的设计缺陷及解决方案是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。