CodeIgniter Framework database transaction processing design flaws and solutions, CodeIgniter framework
Cause:
In one of our online businesses, using older versions of the CodeIgniter framework, there is a design flaw in the DB Class for the DB thing Processing Section, perhaps not a flaw. But he has influenced our production environment and caused a chain reaction. Has a big impact on the business and is not easy to troubleshoot. This question, I in mid-March this year, had to CodeIgniter China's webmaster Hex report, after, I also forgot this matter. To this day, our online business once again thought this problem, the harm I again again. The specific reason, everybody and listen to me to say slowly finish first. (This problem also exists in the latest version 2.1.0)
Analysis:
In the case of CodeIgniter framework version 2.1.0, the 58th row in the Ci_db_driver class of system\database\db_driver.php has a $_trans_status attribute.
Copy the Code code as follows:
system\database\db_driver.php
var $trans _strict = TRUE;
var $_trans_depth = 0;
var $_trans_status = TRUE; Used with transactions to determine if a rollback should occur
var $cache _on = FALSE;
At the same time, in the query method of this class, there is the code that assigns this attribute, see the file 306, 307 line
Copy the Code code as follows:
This would trigger a rollback if transactions is being used
$this->_trans_status = FALSE;
This is also a comment that tells us that if things are handled, then this property will be the deciding condition for a rollback.
In the 520-line thing submission method Trans_complete, the following code:
Copy the Code code as follows:
/**
* Complete Transaction
*
* @access Public
* @return BOOL
*/
function Trans_complete ()
{
if (! $this->trans_enabled)
{
return FALSE;
}
When transactions is nested we only begin/commit/rollback the outermost ones
if ($this->_trans_depth > 1)
{
$this->_trans_depth-= 1;
return TRUE;
}
The query () function would set this flag to FALSE in the event that a query failed
if ($this->_trans_status = = = FALSE)
{
$this->trans_rollback ();
If we are not running in strict mode, we'll reset
The _TRANS_STATUS flag So, subsequent groups of transactions
would be permitted.
if ($this->trans_strict = = = FALSE)
{
$this->_trans_status = TRUE;
}
Log_message (' Debug ', ' DB Transaction Failure ');
return FALSE;
}
$this->trans_commit ();
return TRUE;
}
In 535 rows, if the _trans_status property is False, a rollback occurs and false is returned.
In our business code, due to the programmer's negligence, we did not judge whether the Trans_complete () method was executed correctly and told the user that the operation was successful, but in fact, the program has released the rollback instruction to DB and did not update the DB record successfully. When the user performs the next action, the program discovers that the corresponding record is not updated, and reminds the user that the last action was not completed and notifies the user to re-execute. So repeated ...
The process of troubleshooting, it is also interesting, at first from the PHP code, always can not determine the problem, and did not focus on the Trans_complete () method return. It was not until later Strace grabbed packet analysis that it was known that the rollback was caused by this property.
Copy CodeThe code is as follows:
22:54:08.380085 Write (9, "_\0\0\0\3update ' cfc4n_user_info ' SET ' cfc4n_user_lock ' = 1\nwhere ' cfc4n_user_id ' = \ ' 6154\ ' \ NAND ' cfc4n_user_lock ' = 0 ", 99) = 99//Execute UPDATE command
22:54:08.380089 Read (9, ": \0\0\1\377\36\4#42s22unknown column \ ' cfc4n_user_lock\ ' in \ ' where clause\ '", 16384) = 62//Not present field, SQL execution error
22:54:08.381791 Write (9, "\21\0\0\0\3set autocommit=0", 21) = 21//Prohibit autocommit
22:54:08.381891 Read (9, "\7\0\0\1\0\0\0\0\0\0\0", 16384) = 11
22:54:08.382186 poll ([{fd=9, events=pollin| POLLPRI}], 1, 0) = 0
22:54:08.382258 Write (9, "\v\0\0\0\2jv01_roles", 15) = 15
22:54:08.382343 Read (9, "\7\0\0\1\0\0\0\0\0\0\0", 16384) = 11
22:54:08.382631 poll ([{fd=9, events=pollin| POLLPRI}], 1, 0) = 0
22:54:08.382703 Write (9, "\22\0\0\0\3start TRANSACTION", 22) = 22//Start transaction processing
22:54:08.401954 Write (9, "\v\0\0\0\2database_demo", 15) = 15
22:54:08.402043 Read (9, "\7\0\0\1\0\0\0\1\0\1\0", 16384) = 11
22:54:08.417773 Write (9, "\v\0\0\0\2database_demo", 15) = 15
22:54:08.417872 Read (9, "\7\0\0\1\0\0\0\1\0\0\0", 16384) = 11
22:54:08.418256 Write (9, "[\0\0\0\3update ' Cfc4n_user_info ' SET ' silver ' = CAST (silver + (5) as signed) \nwhere ' Cfc4n_us er_id ' = \ ' 6154\ ' ", 95) = 95//execute other SQL statements
22:54:08.418363 Read (9, "0\0\0\1\0\1\0\1\0\0\0 (Rows matched:1 changed:1 warnings:0", 16384) = 52//Successful update, affects the number of bars 1.
22:54:08.430212 Write (9, "\v\0\0\0\2database_demo", 15) = 15
22:54:08.430314 Read (9, "\7\0\0\1\0\0\0\1\0\0\0", 16384) = 11
22:54:08.430698 Write (9, "B\0\0\0\3update ' cfc4n_user_info ' SET ' exp ' = exp + 26\nwhere ' cfc4n_user_id ' = \ ' 6154\ '", 70) = 70//Execute other SQK statements
22:54:08.430814 Read (9, "0\0\0\1\0\1\0\1\0\0\0 (Rows matched:1 changed:1 warnings:0", 16384) = 52//Successful update, affects the number of bars 1.
22:54:08.432130 Write (9, "\v\0\0\0\2database_demo", 15) = 15
22:54:08.432231 Read (9, "\7\0\0\1\0\0\0\1\0\0\0", 16384) = 11
22:54:08.432602 Write (9, "\244\0\0\0\3update ' cfc4n_user_quest ' SET ' rew ' = 1, ' retable ' = retable + 1, ' re_time ' = 133579 7648\nwhere ' cfc4n_user_id ' = \ ' 6154\ ' \nand ' quest_id ' = \ ' 300001\ ' \nand ' rew ' = 0 ", 168) = 168//Execute other SQK statements
22:54:08.432743 Read (9, "0\0\0\1\0\1\0\1\0\0\0 (Rows matched:1 changed:1 warnings:0", 16384) = 52//Successful update, affects the number of bars 1.
22:54:08.433517 Write (9, "\v\0\0\0\2database_demo", 15) = 15
22:54:08.433620 Read (9, "\7\0\0\1\0\0\0\1\0\0\0", 16384) = 11
22:54:08.433954 Write (9, "\t\0\0\0\3rollback", 13) = 13//ROLLBACK TRANSACTION #注意看这里
22:54:08.434041 Read (9, "\7\0\0\1\0\0\0\0\0\0\0", 16384) = 11
22:54:08.434914 Write (9, "\v\0\0\0\2database_demo", 15) = 15
22:54:08.434999 Read (9, "\7\0\0\1\0\0\0\0\0\0\0", 16384) = 11
22:54:08.435342 Write (9, "\21\0\0\0\3set autocommit=1", 21) = 21//Resume Autocommit
22:54:08.435430 Read (9, "\7\0\0\1\0\0\0\2\0\0\0", 16384) = 11
22:54:08.436923 Write (9, "\1\0\0\0\1", 5) = 5
Can see, at 22:54:08.380085 time point, Send update SQL statement instruction, read return result in 22:54:08.380089 time, get SQL execution error, there is no field "Cfc4n_user_lock"; 22 : 54:08.381791 and 22:54:08.382703 Two point in time, PHP send Stop "autocommit" with "Start transaction" instruction, send "transaction rollback" instruction in 22:54:08.433954.
In conjunction with the code analysis above, it is clear to know because "UPDATE ' cfc4n_user_info ' SET ' cfc4n_user_lock ' = 1 WHERE ' cfc4n_user_id ' = ' 6154′and ' Cfc4n_user_lo CK ' = 0 ' is a SQL execution error that causes the $_trans_status property to be set to false when the code commits the transaction and is judged by the Trans_complete () method that there is a SQL statement execution failure in the "Last Transaction" (which is carefully analyzed below). Decides to roll back the transaction, not commit.
Just mentioned "last transaction", some friends may not understand, we go back to the code, continue to look at the property, also in the Trans_complete method, 542-545 lines:
Copy the Code code as follows:
If we are not running in strict mode, we'll reset
The _TRANS_STATUS flag So, subsequent groups of transactions
would be permitted.
if ($this->trans_strict = = = FALSE)
{
$this->_trans_status = TRUE;
}
can also be easily understood from the note, set up CI designers, in order to more rigorous processing in the same script, there are multiple transactions, the relationship between the transaction is important, one wing wing, one loss. Here the Trans_strict property, is a switch, when Trans_strict is false, is non-strict mode, meaning that the relationship between multiple transactions is not important, does not affect. There is an SQL statement execution failure in the current transaction that does not affect itself. Set the _trans_status to true.
There is no doubt that this is a very thoughtful consideration. Consider the relationships between multiple transactions to ensure that the business runs on more rigorous code.
However, in our code, the wrong SQL statement is executed outside of the transaction, not within the transaction. According to our understanding of the transaction, it is clear that the SQL outside of the transaction is more important than the SQL within the transaction, while the outside can allow for errors, but within the transaction, it must be correct and not be disturbed by the outside world. But in the framework of CI, because the execution of a statement outside a transaction fails, it causes the entire transaction to be rolled back ... Of course, our programmers do not judge the return of the transaction submission method, which is also a problem.
The problem is clear, so the solution must be simple for you.
For example, in the Trans_start method, assign a value to the _trans_status property, set to True, regardless of the issue outside the transaction.
Copy the Code code as follows:
function Trans_start ($test _mode = FALSE)
{
if ($this->trans_strict = = = FALSE)
{
$this->_trans_status = TRUE; Reset the value of this property to True when starting transaction processing
}
2012/05/01 18:00 after CI Chinese community netizen http://codeigniter.org.cn/forums/space-uid-5721.html correction, here to increase trans_strict attribute judgment, In deciding whether to reset _trans_status as well.
if (! $this->trans_enabled)
{
return FALSE;
}
When transactions is nested we only begin/commit/rollback the outermost ones
if ($this->_trans_depth > 0)
{
$this->_trans_depth + = 1;
Return
}
$this->trans_begin ($test _mode);
}
End:
In the case of not understanding the other party's design intentions, can not blindly define the other side of the code evaluation, regardless of the level of the program author. Stronger than himself, nor blind worship, weaker than himself, more can not be criticized; understanding the design intent, learning others excellent design ideas, code style, algorithm efficiency, this is a good habit. Of course the CodeIgniter framework is excellent.
For PHP CodeIgniter Framework
Yesterday just read the CodeIgniter Handbook, in CodeIgniter URLs This chapter has the description method:
This is done by adding rules to the. htaccess file, as follows
Rewriteengine on
Rewritecond $!^ (index\.php|images|robots\.txt)
Rewriterule ^ (. *) $/index.php/$1 [L]
CodeIgniter framework of relevant learning materials, such as manuals, books, tutorials what the
CodeIgniter China-PHP framework CodeIgniter Chinese Community
Click on the user manual, there is a "directory" button above the page, open is a good Chinese manual.
http://www.bkjia.com/PHPjc/848800.html www.bkjia.com true http://www.bkjia.com/PHPjc/848800.html techarticle CodeIgniter Framework database transaction processing design flaws and solutions, CodeIgniter framework Cause: In one of our online business, using the older version of the CodeIgniter framework, where ...