I'm trying to make some SQLÂ Server code also run on MySQL, and I just hit this land mine. Google says the normal approach is to simply do your insert and then select last_insert_ID() to find out what got written.
This does not strike me as safe in a multi-user environment, though. There's a narrow window there where another user could insert something and cause a bad return value. How do I safely insert and obtain the key of the inserted record?
From LAST_INSERT_ID(), LAST_INSERT_ID(expr)
The ID that was generated is maintained in the server on a per-connection basis. This means that the value returned by the function to a given client is the first AUTO_INCREMENT value generated for most recent statement affecting an AUTO_INCREMENT column by that client. This value cannot be affected by other clients, even if they generate AUTO_INCREMENT values of their own. This behavior ensures that each client can retrieve its own ID without concern for the activity of other clients, and without the need for locks or transactions.
So unless your inserts for multiple users would happen to be made over the same database connection, you have nothing to worry about.
Related
This is a problem that has been haunting me for some time. I have a PHP Web application built on Zend. For a particular feature, I have around 10-20 MySQL queries that get executed within a transaction. Out of these queries, some queries are used to delete values and others to insert values. 99% of the time everything works perfectly. But every now and again, some values are not inserted into the tables. I tried the following to debug this but to no avail:
Logged all MySQL queries by setting log = /var/log/mysql/mysql.log in my.cnf file. The necessary queries are being logged and when I execute them manually, the insert takes place correctly.
Checked the return value after the insert and it returns the primary key of the table.
Checked using newrelic if there was any unusual traffic on the server at these instances, but found out that it was ok
It does not seem to be an issue with the code and I am somehow inclined to believe that it has to be some issues with the MySQL DB.
Updated
Another strange thing that I want to mention with regards to this is:
In one table the primary key is auto-incremented. After the insert; the return value gives me the auto incremented value in the log (eg: 32363). But when I check the table, I can find 32362 and 32364, but not 32363.
My understanding is that Apache creates a separate PHP process for each incoming request. That means that if I have code that does something like:
check if record exists
if record doesn't exist, create it
Then this is susceptible to a race condition, is it not? If two requests come in at the same time, and they both hit (1) simultaneously, they will both come back false, and then both attempt to insert a new record.
If so, how do people deal with this? Would creating a MySQL transaction around those 2 requests solve the issue, or do we need to do a full table lock?
As far as I know you cannot create a transaction across different connections. Maybe one solution would be to set column you are checking to be unique. This way if two connections are made to 10, and 10 does not exist. They will both try to create 10. One will finish inserting the row first, and all is well; then the connection just a second behind will fail because the column isn't unique. If you catch the exception that is thrown, then you can subsequently SELECT the record from the database.
Honestly, I've very rarely run into this situation. Often times it can be alleviated by reevaluating business requirements. Even if two different users were trying to insert the exact same data, I would defer management of duplicates the users, rather than the application.
However, if there were a reason to enforce a unique constraint in the application logic, I would use an INSERT IGNORE... ON DUPLICATE KEY UPDATE... query (with the corresponding UNIQUE index in the table, of course).
I think that handling errors on the second step ought to be sufficient. If two processes try to create a record then one of them will fail, as long as youve configured the MySQL table appropriately. Using UNIQUE across the right fields is one way to do the trick.
Apache does not "create a separate PHP process for each incoming request".
It either uses a pool of processes (default, prefork mode), or threads.
The race conditions, as you mention, may also be refered to (or cause) DB "Deadlocks".
#see what is deadlock in a database?
Using transactions where needed should solve this problem, yes.
By making sure you check if a record exists and create it within a transaction, the whole operation is atomic.
Hence, other requests will not try to create duplicate records (or, depending on the actual queries, create inconsistencies or enter actual deadlocks).
Also note that MySQL does not (yet) support nested transactions:
You cannot have transactions inside transactions, as the first commit will commit everything.
I have a problem with a project I am currently working on, built in PHP & MySQL. The project itself is similar to an online bidding system. Users bid on a project, and they get a chance to win if they follow their bid by clicking and cliking again.
The problem is this: if 5 users for example, enter the game at the same time, I get a 8-10 seconds delay in the database - I update the database using the UNIX_TIMESTAMP(CURRENT_TIMESTAMP), which makes the whole system of the bids useless.
I want to mention too that the project is very database intensive (around 30-40 queries per page) and I was thinking maybe the queries get delayed, but I'm not sure if that's happening. If that's the case though, any suggestions how to avoid this type of problem?
Hope I've been at least clear with this issue. It's the first time it happened to me and I would appreciate your help!
You can decide on
Optimizing or minimizing required queries.
You can cache queries do not need to update on each visit.
You can use Summery tables
Update the queries only on changes.
You have to do this cleverly. You can follow this MySQLPerformanceBlog
I'm not clearly on what you're doing, but let me elaborate on what you said. If you're using UNIX_TIMESTAMP(CURRENT_TIMESTAMP()) in your MySQL query you have a serious problem.
The problem with your approach is that you are using MySQL functions to supply the timestamp record that will be stored in the database. This is an issue, because then you have to wait on MySQL to parse and execute your query before that timestamp is ever generated (and some MySQL engines like MyISAM use table-level locking). Other engines (like InnoDB) have slower writes due to row-level locking granularity. This means the time stored in the row will not necessarily reflect the time the request was generated to insert said row. Additionally, it can also mean that the time you're reading from the database is not necessarily the most current record (assuming you are updating records after they were inserted into the table).
What you need is for the PHP request that generates the SQL query to provide the TIMESTAMP directly in the SQL query. This means the timestamp reflects the time the request is received by PHP and not necessarily the time that the row is inserted/updated into the database.
You also have to be clear about which MySQL engine you're table is using. For example, engines like InnoDB use MVCC (Multi-Version Concurrency Control). This means while a row is being read it can be written to at the same time. If this happens the database engine uses something called a page table to store the existing value that will be read by the client while the new value is being updated. That way you have guaranteed row-level locking with faster and more stable reads, but potentially slower writes.
I have a question about concurrency control and when to worry about that. I've created a PHP/MySQL site (InnoDB). I know about how to avoid transaction issues but then there is the concurrency control.
My site is an E-commerce which holds user inserted goods, so as an example. When a user inserts a new item to the site, the DB creates an productId as primary key ( auto-incremented by the DB ) and stores the other data about that product that is submitted through a form. I'm using prepared statements.
Do i have to worry about if two or more users are doing this at exactly the same time? Is there any chance that submitting two or more items at the same time will mess up the data of the different rows?
To be able to insert products the user has to be logged in using sessions if that matters to the question.
Thanks in advance, Markus.
Unless they're writing to the exact same row, I don't see why you'd have any concurrency issues. Even if they were writing to the same row, InnoDB has row-level locking in place, which means that a row will be locked until a user has finished writing to it, leaving subsequent users to "wait it out" until the lock has been released. In regards to the possibility of "conflicting INSERT queries": If you're inserting new data into a table that is using an auto-incrementing primary key, you're guaranteed to get a unique ID each time, which means that concurrency should never be an issue on INSERT.
You can do fast table locking especially useful on updates
Is it possible to queue client requests for accessing database in MySQL. I am trying to do this for concurrency management. MySQL Locks can be used but somehow I am not able to get the desired outcome.
Effectively what I am trying to do is:
INSERT something in a new row
SELECT a column from that row
Store that value in a variable
The issue comes up when two different clients INSERT at the same time, thus variables for both clients store the value of the last INSERT.
I worked the following alternative, but it failed in a few test runs, and the bug is quite evident:
INSERT
LOCK Table
SELECT
Store
UNLOCK
Thanks!
My best guess is that you have an auto-increment column and want to get its value after inserting a row. One option is to use LAST_INSERT_ID() (details here and here).
If this is not applicable, then please post some more details. What exactly are you trying to do and what queries are being fired?