I am facing a data loss issue when trying to insert data via multiple batch job.
We have 10.1.21-MariaDB-1~jessie version with InnoDB as the default storage engine.
I have faced the following scenario where :
Column value is missing though value was present in generated insert statement for one of the record from the database.
Table auto incremented index value shows number 170, but the actual record count was 165. So here 5 records were lost completely .
Can anyone guide me on this to fix this issue ?
Case 1: You have outside knowledge of 5 particular rows being missing. Need more info to discuss this one.
Case 2: COUNT(*) says 165 but SHOW TABLE STATUS says 170 "Rows". Don't trust the STATUS; it is an estimate.
Case 3: You did a bunch of INSERTs, COUNT(*) says 150, SHOW CREATE TABLE says AUTO_INCREMENT = 170. This says that some ids were "burned". All types of INSERT statements (multi-row, IGNORE, IODKU, REPLACE, ROLLBACK, etc) have ways of burning ids. Live with it. If you describe your statements, we can go into more detail.
My bad .... There was no such concurrency issue. In one of the scenario batch job was removing some records. And those job executions were not sequential , after proper sequential job chaining able to trace this issue.
Related
I'm creating a database which requires several fields to be unique, and was wondering which method is least expensive in terms of checking that uniqueness?
Query the database with a mysqli() call to check if a value exists?
Use PHP to download a file of all entries, then check that file and delete afterwards
Set columns to "Unique" index
If the best option (which I'm assuming it is) is to set the columns to unique, then how do you go about handling the error that gets thrown when the value already exists without breaking out of the function? Or is that even possible?
Querying the database first risks race conditions. That is, you SELECT to verify the value isn't already there, so you can INSERT it. But unfortunately, in the brief moment between your SELECT and your INSERT, someone else slips in and inserts the value you were going to add. So you end up having to catch the error anyway.
This may seem unlikely, but there's some old wisdom: "one in a million is next Tuesday." I.e. when we process millions of transactions per day, even a rare fluke is bound to happen sooner than we think.
This is right out. What happens when the set of entries is 10 million long? 100 million? 1 billion? This solution doesn't scale, so just put it out of your mind immediately.
Yes, use a UNIQUE constraint. Attempt the INSERT and handle the error. This avoids a race condition, because your INSERT's unique check is atomic. That is, no one can slip in between the clock ticks to add a value before you can insert it.
One caveat of this: in MySQL's InnoDB storage engine, if you try an INSERT and it fails due to conflicting with a UNIQUE constraint (or other reason for failure), it doesn't reverse its allocation of the next auto-increment value. The row is not inserted, but the auto-inc value is generated and discarded. So if you have frequent cases of such failures, you could end up skipping a lot of integers in your primary key. I had one case where my customer actually ran out of integers because they were skipping 1500 id values for each row that was successfully inserted. In their case, I suggested using your solution 1, then try the insert only if they are "pretty sure" of a safe insert, but then they have to handle the error anyway just in case of the race condition.
Handling the error means checking the return value every time you execute an SQL query. I can't tell you how many questions I read on StackOverflow where programmers fail to check that execute() returned false, and they wonder why their INSERT failed.
The quick answer is let the database do it if at all possible.
The slower answer depends on how you want to handle exceptions to your uniqueness requirement.
If you never need to over-ride the uniqueness requirement, you can use a UNIQUE index in MySQL. Then you can use "ON DUPLICATE KEY" to handle the exceptions.
However, if you sometimes need to allow a duplicate, you can't use a UNIQUE key and you'd be best using a regular INDEX and doing a query first to see if the value exists before you insert it.
Well, the least expensive is one point, the user experience is another.
I would personnaly go for a query (with a custom message if key is found) AND a Unique constraint (to have a consistent db). So 1 + 3.
But if you want less expensive, just go to the unique constraint, and try to build some comprehensive error message, using the error message from mysqli_error.
So 1 + 3 or 3, but not 2.
I have an invoices table (innoDB) in which i need to set manually the progressive number for the next invoice. My code now is
SELECT MAX(invoice_n) FROM invoices WHERE invoice_y = 2013
and then regulary save the record putting the new invoice_n = max + 1. I have an index UNIQUE on invoice_n-invoice_y and I'm logging db errors, so I see that sometimes I have duplicate key entry errors, beacuse I have hundreds of different users connected. I put the code in a loop that continues until the invoice is generated, but i think there can be a more elegant solution, especially using transactions. I read a bit but I can't understand how can I achive my result with transactions.
Any help?
You could use "AUTO_INCREMENT" in your column definition. You will see some gaps between numbers if the insertion fails.
Another alternative is creating a table with the last index per year (see comments) and follow the next steps:
Begin a transaction
Select for update last index per row and increment in one
Insert the new invoice
Commit your transaction
Some links:
See MySQL (InnoDB) transaction model
Locking example
Another one, you could use an "optimistic approach" and repeat the select and the insert if it fails because a duplicate key.
I hope this helps you, any comment is welcome!
A co-worker just made me aware of a very strange MySQL behavior.
Assuming you have a table with an auto_increment field and another field that is set to unique (e.g. a username-field). When trying to insert a row with a username thats already in the table the insert fails, as expected. Yet the auto_increment value is increased as can be seen when you insert a valid new entry after several failed attempts.
For example, when our last entry looks like this...
ID: 10
Username: myname
...and we try five new entries with the same username value on our next insert we will have created a new row like so:
ID: 16
Username: mynewname
While this is not a big problem in itself it seems like a very silly attack vector to kill a table by flooding it with failed insert requests, as the MySQL Reference Manual states:
"The behavior of the auto-increment mechanism is not defined if [...] the value becomes bigger than the maximum integer that can be stored in the specified integer type."
Is this expected behavior?
InnoDB is a transactional engine.
This means that in the following scenario:
Session A inserts record 1
Session B inserts record 2
Session A rolls back
, there is either a possibility of a gap or session B would lock until the session A committed or rolled back.
InnoDB designers (as most of the other transactional engine designers) chose to allow gaps.
From the documentation:
When accessing the auto-increment counter, InnoDB uses a special table-level AUTO-INC lock that it keeps to the end of the current SQL statement, not to the end of the transaction. The special lock release strategy was introduced to improve concurrency for inserts into a table containing an AUTO_INCREMENT column
…
InnoDB uses the in-memory auto-increment counter as long as the server runs. When the server is stopped and restarted, InnoDB reinitializes the counter for each table for the first INSERT to the table, as described earlier.
If you are afraid of the id column wrapping around, make it BIGINT (8-byte long).
Without knowing the exact internals, I would say yes, the auto-increment SHOULD allow for skipped values do to failure inserts. Lets say you are doing a banking transaction, or other where the entire transaction and multiple records go as an all-or-nothing. If you try your insert, get an ID, then stamp all subsequent details with that transaction ID and insert the detail records, you need to ensure your qualified uniqueness. If you have multiple people slamming the database, they too will need to ensure they get their own transaction ID as to not conflict with yours when their transaction gets committed. If something fails on the first transaction, no harm done, and no dangling elements downstream.
Old post,
but this may help people,
You may have to set innodb_autoinc_lock_mode to 0 or 2.
System variables that take a numeric value can be specified as --var_name=value on the command line or as var_name=value in option files.
Command-Line parameter format:
--innodb-autoinc-lock-mode=0
OR
Open your mysql.ini and add following line :
innodb_autoinc_lock_mode=0
I know that this is an old article but since I also couldn't find the right answer, I actually found a way to do this. You have to wrap your query within an if statement. Its usually insert query or insert and on duplicate querys that mess up the organized auto increment order so for regular inserts use:
$check_email_address = //select query here\\
if ( $check_email_address == false ) {
your query inside of here
}
and instead of INSERT AND ON DUPLICATE use a UPDATE SET WHERE QUERY in or outside an if statement doesn't matter and a REPLACE INTO QUERY also does seem to work
A co-worker just made me aware of a very strange MySQL behavior.
Assuming you have a table with an auto_increment field and another field that is set to unique (e.g. a username-field). When trying to insert a row with a username thats already in the table the insert fails, as expected. Yet the auto_increment value is increased as can be seen when you insert a valid new entry after several failed attempts.
For example, when our last entry looks like this...
ID: 10
Username: myname
...and we try five new entries with the same username value on our next insert we will have created a new row like so:
ID: 16
Username: mynewname
While this is not a big problem in itself it seems like a very silly attack vector to kill a table by flooding it with failed insert requests, as the MySQL Reference Manual states:
"The behavior of the auto-increment mechanism is not defined if [...] the value becomes bigger than the maximum integer that can be stored in the specified integer type."
Is this expected behavior?
InnoDB is a transactional engine.
This means that in the following scenario:
Session A inserts record 1
Session B inserts record 2
Session A rolls back
, there is either a possibility of a gap or session B would lock until the session A committed or rolled back.
InnoDB designers (as most of the other transactional engine designers) chose to allow gaps.
From the documentation:
When accessing the auto-increment counter, InnoDB uses a special table-level AUTO-INC lock that it keeps to the end of the current SQL statement, not to the end of the transaction. The special lock release strategy was introduced to improve concurrency for inserts into a table containing an AUTO_INCREMENT column
…
InnoDB uses the in-memory auto-increment counter as long as the server runs. When the server is stopped and restarted, InnoDB reinitializes the counter for each table for the first INSERT to the table, as described earlier.
If you are afraid of the id column wrapping around, make it BIGINT (8-byte long).
Without knowing the exact internals, I would say yes, the auto-increment SHOULD allow for skipped values do to failure inserts. Lets say you are doing a banking transaction, or other where the entire transaction and multiple records go as an all-or-nothing. If you try your insert, get an ID, then stamp all subsequent details with that transaction ID and insert the detail records, you need to ensure your qualified uniqueness. If you have multiple people slamming the database, they too will need to ensure they get their own transaction ID as to not conflict with yours when their transaction gets committed. If something fails on the first transaction, no harm done, and no dangling elements downstream.
Old post,
but this may help people,
You may have to set innodb_autoinc_lock_mode to 0 or 2.
System variables that take a numeric value can be specified as --var_name=value on the command line or as var_name=value in option files.
Command-Line parameter format:
--innodb-autoinc-lock-mode=0
OR
Open your mysql.ini and add following line :
innodb_autoinc_lock_mode=0
I know that this is an old article but since I also couldn't find the right answer, I actually found a way to do this. You have to wrap your query within an if statement. Its usually insert query or insert and on duplicate querys that mess up the organized auto increment order so for regular inserts use:
$check_email_address = //select query here\\
if ( $check_email_address == false ) {
your query inside of here
}
and instead of INSERT AND ON DUPLICATE use a UPDATE SET WHERE QUERY in or outside an if statement doesn't matter and a REPLACE INTO QUERY also does seem to work
How do you stop race conditions in MySQL? the problem at hand is caused by a simple algorithm:
select a row from table
if it doesn't exist, insert it
and then either you get a duplicate row, or if you prevent it via unique/primary keys, an error.
Now normally I'd think transactions help here, but because the row doesn't exist, the transaction don't actually help (or am I missing something?).
LOCK TABLE sounds like an overkill, especially if the table is updated multiple times per second.
The only other solution I can think of is GET_LOCK() for every different id, but isn't there a better way? Are there no scalability issues here as well? And also, doing it for every table sounds a bit unnatural, as it sounds like a very common problem in high-concurrency databases to me.
what you want is LOCK TABLES
or if that seems excessive how about INSERT IGNORE with a check that the row was actually inserted.
If you use the IGNORE keyword, errors
that occur while executing the INSERT
statement are treated as warnings
instead.
It seems to me you should have a unique index on your id column, so a repeated insert would trigger an error instead of being blindingly accepted again.
That can be done by defining the id as a primary key or using a unique index by itself.
I think the first question you need to ask is why do you have many threads doing the exact SAME work? Why would they have to insert the exact same row?
After that being answered, I think that just ignoring the errors will be the most performant solution, but measure both approaches (GET_LOCK v/s ignore errors) and see for yourself.
There is no other way that I know of. Why do you want to avoid errors? You still have to code for the case when another type of error occurs.
As staticsan says transactions do help but, as they usually are implied, if two inserts are ran by different threads, they will both be inside an implied transactions and see consistent views of the database.
Locking the entire table is indeed overkill. To get the effect that you want, you need something that the litterature calls "predicate locks". No one has ever seen those except printed on the paper that academic studies are published on. The next best thing are locks on the "access paths" to the data (in some DBMS's : "page locks").
Some non-SQL systems allow you to do both (1) and (2) in one single statement, more or less meaning the potential race conditions arising from your OS suspending your execution thread right between (1) and (2), are entirely eliminated.
Nevertheless, in the absence of predicate locks such systems will still need to resort to some kind of locking scheme, and the finer the "granularity" (/"scope") of the locks it takes, the better for concurrency.
(And to conclude : some DBMS's - especially the ones you don't have to pay for - do indeed offer no finer lock granularity than "the entire table".)
On a technical level, a transaction will help here because other threads won't see the new row until you commit the transaction.
But in practice that doesn't solve the problem - it only moves it. Your application now needs to check whether the commit fails and decide what to do. I would normally have it rollback what you did, and restart the transaction because now the row will be visible. This is how transaction-based programmer is supposed to work.
I ran into the same problem and searched the Net for a moment :)
Finally I came up with solution similar to method to creating filesystem objects in shared (temporary) directories to securely open temporary files:
$exists = $success = false;
do{
$exists = check();// select a row in the table
if (!$exists)
$success = create_record();
if ($success){
$exists = true;
}else if ($success != ERROR_DUP_ROW){
log_error("failed to create row not 'coz DUP_ROW!");
break;
}else{
//probably other process has already created the record,
//so try check again if exists
}
}while(!$exists)
Don't be afraid of busy-loop - normally it will execute once or twice.
You prevent duplicate rows very simply by putting unique indexes on your tables. That has nothing to do with LOCKS or TRANSACTIONS.
Do you care if an insert fails because it's a duplicate? Do you need to be notified if it fails? Or is all that matters that the row was inserted, and it doesn't matter by whom or how many duplicates inserts failed?
If you don't care, then all you need is INSERT IGNORE. There is no need to think about transactions or table locks at all.
InnoDB has row level locking automatically, but that applies only to updates and deletes. You are right that it does not apply to inserts. You can't lock what doesn't yet exist!
You can explicitly LOCK the entire table. But if your purpose is to prevent duplicates, then you are doing it wrong. Again, use a unique index.
If there is a set of changes to be made and you want an all-or-nothing result (or even a set of all-or-nothing results within a larger all-or-nothing result), then use transactions and savepoints. Then use ROLLBACK or ROLLBACK TO SAVEPOINT *savepoint_name* to undo changes, including deletes, updates and inserts.
LOCK tables is not a replacement for transactions, but it is your only option with MyISAM tables, which do not support transactions. You can also use it with InnoDB tables if row-level level locking isn't enough. See this page for more information on using transactions with lock table statements.
I have a similar issue. I have a table that under most circumstances should have a unique ticket_id value, but there are some cases where I will have duplicates; not the best design, but it is what it is.
User A checks to see if the ticket is reserved, it isn't
User B checks to see if the ticket is reserved, it isn't
User B inserts a 'reserved' record into the table for that ticket
User A inserts a 'reserved' record into the table for that ticket
User B check for duplicate? Yes, is my record newer? Yes, leave it
User A check for duplicate? Yes, is my record newer? No, delete it
User B has reserved the ticket, User A reports back that the ticket has been taken by someone else.
The key in my instance is that you need a tie-breaker, in my case it's the auto-increment id on the row.
In case insert ignore doesnt fit for you as suggested in the accepted answer , so according to the requirements in your question :
1] select a row from table
2] if it doesn't exist, insert it
Another possible approach is to add a condition to the insert sql statement,
e.g :
INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'Rupert', 'Somewhere', '022') AS tmp
WHERE NOT EXISTS (
SELECT name FROM table_listnames WHERE name = 'Rupert'
) LIMIT 1;
Reference:
https://stackoverflow.com/a/3164741/179744