I am setting category and subcategory of an entity so the subcategory depends on category. While setting categories, I need to ensure that either the complete transaction is successful or complete transaction is failed. If I use 2 queries together, it will still execute one by one and thus this is possible that one insert is successful and other is not. Is there any good way of maintaining atomicity in php or mysqli?
No, because those databases are completely unrelated and there's no such thing as a cross-database transaction. At least, not that I know of. :)
But you are a little bit safer if you use transactions for your inserts. Start a transaction on both databases, do all modifications on both databases, and only afterwards commit both transactions. The insert/update statements will already have run (and probably have failed if there were problems like constraint errors), so at the point where you're about to commit, there's not much that's likely to go wrong.
Make sure your tables are in a storage type that supports transactions.
[edit]
You can use
START TRANSACTION;
-- Your queries go here
COMMIT
To execute statements within a transaction.
For more info, read: http://dev.mysql.com/doc/refman/5.0/en/commit.html
Related
I have a table with user login information and registration too. So when two users consecutively try to add their details:
Will both the writes clashes and the table wont be updated?
Using threads for these writes is bad idea. As for each write a new thread would be created and it would clog the server. Is the server responsible for it to manage on its own?
Is locking the table a good idea?
My back-end runs on PHP/Apache with MySQL (InnoDB) for the database.
Relational databases are designed to avoid these kinds of conditions. You don't need to worry about them unless you are designing your own relational database from scratch.
In short, just know this: Any time a write is initiated, there is a row-level lock. If another transaction wants to write to that same row, then it has to wait until the first transaction releases the lock. This is a fundamental part of relational databases. You don't need to add a lock because they've already thought of that :)
You can read more about how MySQL performs locks to avoid deadlocking and other transaction errors here.
If you're really paranoid about this, or perhaps you are doing multiple things when you register a user and need them done atomically, you might want to look at using Transactions in MySQL. There's a decent write-up about Transactions here http://www.mysqltutorial.org/mysql-transaction.aspx
BEGIN;
do related reads/writes to the data
COMMIT;
Inside that "transaction", the connection sees a consistent view of the data, and blocks anyone else from messing with that view.
There are exceptions. The main one is
BEGIN
SELECT ... FOR UPDATE;
fiddle with the values SELECTed
UPDATE ...; -- and change those values
COMMIT;
The SELECT .. FOR UPDATE announces what should not be tampered with. If another connection wants to mess with the same rows, it will have to wait until your COMMIT, at which time he may find that things have changed and he will need to do something different. But, in general, this avoids a "deadlock" wherein two transactions are stepping on each other so badly that one has to be "rolled back".
With techniques like this, the "concurrency" is prevented only briefly and relatively precisely. That is, if two connections are working with different rows, both can proceed -- there is no need to "prevent concurrency".
I currently use InnoDB transactions to manage the effect of any single webpage request. One request per transaction. This works well if the request fails I can just ignore it.
As a relative newbie to MySQL administration, I remain worried that something I write into my PHP code will do something bad to my database. DELETE FROM or UPDATE without a where statement or something as an extreme example. The idea of the transactions is that when I inevitably notice what happened later, after the bad transaction is committed, I should be able to rollback the mistake.
However, the database is used heavily, so its likely that other transactions will come in between when I commit the bad transaction and when I notice it and go to act on it. But all the documentation I have seen on transactions, and the AWS restore-to-point-in-time, only allow you "go back" to before a transaction is committed.
So, how do I recover or "roll-forward" the transactions that came in after my bad one? They are in the InnoDB log, so should I be able to apply the later transactions again, just skipping the one bad one? My software interfaces with an external credit card processor, so just losing those later transactions isn't an option.
I have a hard time imagining its impossible, but I can't find any way to "roll-forward". Is this possible? Is it something you have to write into the database structure itself, like keeping a history table with triggers and using a history record to update after rolling back?
Confused.
Can I use this function instead of 'LOCK TABLES' query?
Example:
pdo::beginTransaction();
SELECT id AS last_id FROM t WHERE...
INSERT INTO t (id,...) VALUES (last_id+1,....)
pdo::commit();
The answer to this is: it depends. The most important resource to help you understand exactly what it does, what it doesn't do and how it works is here.
For a kick off, it depends whether the underlying driver (MySQL, MSSQL, etc etc) supports transaction functionality at all. If the driver does not support transactions, pdo::beginTransaction(); will fail and return FALSE, and all your queries will be executed immediately. This is not to say that a LOCK TABLES query would fail - it depends whether the underlying database engine supports it.
In fact, in MySQL at least, pdo::beginTransaction() follows the same rules as a START TRANSACTION statement. It does not lock the table immediately, it simply ensures that that the queries that are part of the transaction follow the rules of ACID.
I won't go into full details of exactly which combinations will work and what they will do because they are covered at length in the documents I linked to, and there is no sense in me writing it all out again.
I'm sorry, this is a very general question but I will try to narrow it down.
I'm new to this whole transaction thing in MySQL/PHP but it seems pretty simple. I'm just using mysql not mysqli or PDO. I have a script that seems to be rolling back some queries but not others. This is uncharted territory for me so I have no idea what is going on.
I start the transaction with mysql_query('START TRANSACTION;'), which I understand disables autocommit at the same time. Then I have a lot of complex code and whenever I do a query it is something like this mysql_query($sql) or $error = "Oh noes!". Then periodically I have a function called error_check() which checks if $error is not empty and if it isn't I do mysql_query('ROLLBACK;') and die($error). Later on in the code I have mysql_query('COMMIT;'). But if I do two queries and then purposely throw an error, I mean just set $error = something, it looks like the first query rolls back but the second one doesn't.
What could be going wrong? Are there some gotchas with transactions I don't know about? I don't have a good understanding of how these transactions start and stop especially when you mix PHP into it...
EDIT:
My example was overly simplified I actually have at least two transactions doing INSERT, UPDATE or DELETE on separate tables. But before I execute each of those statements I backup the rows in corresponding "history" tables to allow undoing. It looks like the manipulation of the main tables gets rolled back but entries in the history tables remain.
EDIT2:
Doh! As I finished typing the previous edit it dawned on me...there must be something wrong with those particular tables...for some reason they were all set as MyISAM.
Note to self: Make sure all the tables use transaction-supporting engines. Dummy.
I'd recommend using the mysqli or PDO functions rather than mysql, as they offer some worthwhile improvements—especially the use of prepared statements.
Without seeing your code, it is difficult to determine where the problem lies. Given that you say your code is complex, it is likely that the problem lies with your code rather than MySQL transactions.
Have you tried creating some standalone test scripts? Perhaps you could isolate the SQL statements from your application, and create a simple script which simply runs them in series. If that works, you have narrowed down the source of the problem. You can echo the SQL statements from your application to get the running order.
You could also try testing the same sequence of SQL statements from the MySQL client, or through PHPMyAdmin.
Are your history tables in the same database?
Mysql transactions only work using the mysqli API (not the classic methods). I have been using transactions. All I do is deactivate autocommit and run my SQL statements.
$mysqli->autocommit(FALSE);
SELECT, INSERT, DELETE all are supported. as long as Im using the same mysqli handle to call these statements, they are within the transaction wrapper. nobody outside (not using the same mysqli handle) will see any data that you write/delete using INSERT/DELETE as long as the transaction is still open. So its critical you make sure every SQL statement is fired with that handle. Once the transaction is committed, data is made available to other db connections.
$mysqli->commit();
For fun I am replacing the mysqli extension in my app with PDO.
Once in awhile I need to use transactions + table locking.
In these situations, according to the mysql manual, the syntax needs to be a bit different. Instead of calling START TRANSACTION, you do it like so...
SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;
(http://dev.mysql.com/doc/refman/5.0/en/lock-tables-and-transactions.html)
My question is, how does this interact with PDO::beginTransaction? Can I use PDO::beginTransaction in this case? Or should I manually send the sql "SET autocommit = 0; ... etc".
Thanks for the advice,
When you call PDO::beginTransaction(), it turns off auto commit.
So you can do:
$db->beginTransaction();
$db->exec('LOCK TABLES t1, t2, ...');
# do something with tables
$db->commit();
$db->exec('UNLOCK TABLES');
After a commit() or rollBack(), the database will be back in auto commit mode.
I have spent a huge amount of time running around this issue, and the PHP documentation in this area is vague at best. A few things I have found, running PHP 7 with a MySQL InnoDB table:
PDO::beginTransaction doesn't just turn off autocommit, having tested the answer provided by Olhovsky with code that fails, rollbacks do not work; there is no transactional behaviour. This means it can't be this simple.
Beginning a transaction may be locking the used tables... I eagerly await for someone to tell me I'm wrong with this, but here are the reasons it could be: This comment, which shows a table being inaccessible when a transaction has started, without being locked. This PHP documentation page, that slips in on the end:
... while the transaction is active, you are guaranteed that no one else can make changes while you are in the middle of your work
To me this behaviour is quite smart, and also provides enough wiggle room for PDO to cope with every database, which is after all the aim. If this is what is going on though, its just massively under documented and should've been called something else to avoid confusion with a true database transaction, which doesn't imply locking.
Charles' answer I think is probably the best if you are after certainty with a workload that will require high concurrency; do it by hand using explicit queries to the database, then you can go by the database's documentation.
Update
I have had a production server up and running using the PDO transaction functions for a while now, recently using AWS's Aurora database (fully compatible with MySQL but built to automatically scale etc). I have proven these two points to myself:
Transactions (purely the ability to commit all database changes together) work using PDO::beginTransaction(). In short, I know many scripts have failed half way through their database select/updates and data integrity has been maintained.
Table locking isn't happening, I've had an index duplication error to prove this.
So, to further my conclusion, looks like the behaviour of these functions seems to change based on database engine (and possibly other factors). As far as I can tell both from experience and the documentation, there is no way to know programmatically what is going on... whoop...
In MySQL, beginning a transaction is different than turning off autocommit, due to how LOCK/UNLOCK TABLES works. In MySQL, LOCK TABLES commits any open transactions, but turning off autocommit isn't actually starting a transaction. MySQL is funny that way.
In PDO, starting a transaction using beginTransaction doesn't actually start a new transaction, it just turns off autocommit. In most databases, this is sane, but it can have side effects with MySQL's mentioned behavior.
You probably should not rely on this behavior and how it interacts with MySQL's quirks. If you're going to deal with MySQL's behavior for table locking and DDL, you should avoid it. If you want autocommit off, turn it off by hand. If you want to open a transaction, open a transaction by hand.
You can freely mix the PDO API methods of working with transactions and SQL commands when not otherwise working with MySQL's oddities.