Unique Codes - Given to two users who hit script in same second - php

Hi have a bunch of unique codes in a database which should only be used once.
Two users hit a script which assigns them at the same time and got the same codes!
The script is in Magento and the user can order multiple codes. The issue is if one customer orders 1000 codes the script grabs the top 1000 codes from the DB into an array and then runs through them setting them to "Used" and assigning them to an order. If a second user hits the same script at a similar time the script then grabs the top 1000 codes in the DB at that point in time which crosses over as the first script hasn't had a chance to finish assigning them.
This is unfortunate but has happened quite a few times!
My idea was to create a new table, once the user hits the script a row is made with "order_id" "code_type". Then in the same script a check is done so if a row is in this new table and the "code_type" matches that of which the user is ordering it will wait 60 seconds and check again until the previous codes are issued and the table is empty where it will then create a row and off it goes.
I am not sure if this is the best way or if two users hit at the same second again whether two rows will just be inserted and off we go with the same problem!
Any advice is much appreciated!

The correct answer depends on the database you use.
For example in MySQL with InnoDB the possible solution is a transaction with SELECT ... LOCK IN SHARE MODE.
Schematically it works this by firing following queries:
START TRANSACTION;
SELECT * FROM codes WHERE used = 0 LIMIT 1000 LOCK IN SHARE MODE;
// save ids
UPDATE codes SET used=1 WHERE id IN ( ...ids....);
COMMIT;
More information at http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html

Related

Issue with maintaining a MySQL WooCommerce Customer Table

Well, I'm afraid that I will not be able to post a minimum reproducible example, and for that I apologize. But, here goes nothing.
Ours is a weekly prepared meals service. I track order volume in many ways. Here is the structure of the relevant table:
So then I utilize the highlighted fields in many ways, such as indicating to delivery drivers if a customer is returning from the prior order being more than a month ago (last_order_w - prev_order_w > 4), for instance.
Lately I have been noticing that the data is not consistently updating properly. In the past 3 weeks, I would say it is an occurrence of 5%. If it were more consistent, I would be more confident in my ability to track down the issue, but I am not even sure how to provoke it, as I only really notice it after the fact.
The code that should cause the update is below:
<?php
//retrieve and iterate over IDs of orders placed since last synchronization.
$newOrders=array_map('reset',$dbh->query("select id from wp_posts where id > (select max(synced) from fitaf_weeks) and post_type='shop_order' and post_status='wc-processing'")->fetchAll(PDO::FETCH_NUM));
foreach($newOrders as $no){
//retrieve the metadata for the current order
$newMetas=array_map('reset',$dbh->query("select meta_key,meta_value from wp_postmeta where post_id=$no")->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_UNIQUE));
//check if the current order is associated with an existing customer
$exist=$dbh->query("select * from fitaf_customers where id=".$newMetas['_customer_user'])->fetch();
//if not, gather the information we want to store from this post
$noExist=[$newMetas['_customer_user'],$newMetas['_shipping_first_name'],$newMetas['_shipping_last_name'],$newMetas['_shipping_address_1'],(strlen($newMetas['_shipping_address_2'])==0?NULL:$newMetas['_shipping_address_2']),$newMetas['_shipping_city'],$newMetas['_shipping_state'],$newMetas['_shipping_postcode'],$phone,$newMetas['_billing_email'],1,1,$no,$newMetas['_paid_date'],$week[3],$newMetas['_order_total']];
if($exist){
//if we found a record in the customer table, retrieve the data we want to modify
$oldO=$dbh->query("select last_order_id,last_order,last_order_w,lo,num_orders from fitaf_customers where id=".$newMetas['_customer_user'])->fetch(PDO::FETCH_GROUP|PDO::FETCH_ASSOC|PDO::FETCH_UNIQUE);
//make changes to the retrieved data, and make sure we are storing the most recently used delivery address and prepare the data points for the update command
$exists=[$phone,$newMetas['_shipping_first_name'],$newMetas['_shipping_last_name'],$newMetas['_shipping_postcode'],$newMetas['_shipping_address_1'],(strlen($newMetas['_shipping_address_2'])==0?NULL:$newMetas['_shipping_address_2']),$newMetas['_shipping_city'],$newMetas['_shipping_state'],$newMetas['_paid_date'],$no,$week[3],$oldO['last_order'],$oldO['last_order_id'],$oldO['last_order_w'],($oldO['num_orders']+1),($oldO['lo']+$newMetas['_order_total']),$newMetas['_customer_user']];
}
if(!$exist){
//if the customer did not exist, perform an insert
$dbh->prepare("insert into fitaf_customers(id,fname,lname,addr1,addr2,city,state,zip,phone,email,num_orders,num_weeks,last_order_id,last_order,last_order_w,lo) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")->execute($noExist);
}
else{
//if the customer did exist, update their data
$dbh->prepare("update fitaf_customers set phone=?,fname=?,lname=?,zip=?,addr1=?,addr2=?,city=?,`state`=?,last_order=?,last_order_id=?,last_order_w=?,prev_order=?,prev_order_id=?,prev_order_w=?,num_orders=?,lo=? where id=?")->execute($exists);
}
}
//finally retrieve the most recent post ID and update the field we check against when the syncornization script runs
$lastPlaced=$dbh->query('select max(id) from wp_posts where post_type="shop_order"')->fetch()[0];
$updateSync=$dbh-> query("update fitaf_weeks set synced=$lastPlaced order by id desc limit 1");
?>
Unfortunately I don't have any relevant error logs to show, however, as I documented the code for this post, I realized a potential shortcoming. I should be utilizing the data retrieved from the initial query of new posts, rather than a selecting the highest post id after performing this logic. However, I have timers running on my scripts, and this section hasn't taken over 3 seconds to run in a long time. So it seems unlikely, that the script, which runs on a cron every 5 minutes, is experiencing this unintended overlap?
While I have made the change to pop the highest ID off of $newOrders, and hope it solves the issue, I am still curious to see if anyone has any insights on what could cause this logic to fail at such a low occurrence.
It seems likely your problem comes from race conditions between multiple operations accessing your db.
First of all, your last few lines of code do SELECT MAX(ID) and then uses that value to update something. You Can't Do Thatâ„¢. If somebody else adds a row to that wp_posts table anytime after the entry you think is relevant, you'll use the wrong ID. I don't understand your app well enough to recommend a fix. But I do know this is a serious and notorious problem.
You have another possible race condition as well. Your logic is this:
SELECT something.
make a decision based on what you SELECTED.
INSERT or UPDATE based on that decision.
If some other operation, done by some other user of the db, intervenes between step 1 and step 3, your decision might be wrong.
You fix this with a db transaction. The ->beginTransaction() operation, well, begins the transaction. The ->commit() operation concludes it. And, the SELECT operation you use for step one should say SELECT ... FOR UPDATE.

configure mysql database to save progress

I am new in forum, and need some help to do one functionality for my unity game. I am trying to save the progress of the player in one mysql database, like this:
userid level stars
29 1 2
29 2 1
45 1 3
50 1 2
50 2 3
50 3 1
29 3 3
so the script send the userid provided by the user registration in the begining of the game. and for each level he complete, the script send the number of the level, and the amount of stars collected in the level..
the problem and question is, how I configure this in the php script and mysql database to save the information only once? because if the player with the id 50 play the first level, will add a line with the information, but if the same player play the first level again and change the amount of stars, I dont want a new line, just update the stars amount.
I take a look in the INDEX, UNIQUE, PRIMARY, FULLTEXT, SPATIAL functions but dont figured out what is the correct combination and how to put in the php script, and take a look in other questions in the forum but nothing like this.
thanks for the help!
I recommend you use http://redis.io/ (in-memory data structure store, used as database, cache and message broker) to save progress in the games.
First you want an unique index on the combination (userid, level) and then you want to do an update if the combination exists and an insert otherwise.
For how to create the unique index please take a look at How do I specify unique constraint for multiple columns in MySQL?
For how to code the SQL query to do update/insert please take a look at SQL: If Exists Update Else Insert
The article above uses Microsoft SQL syntax. In PHP you can code this by issuing the query and then using mysql_affected_rows to see how many rows where affected. If 0 rows where affected then you issue the INSERT query from PHP.
in pseudo code you need to do something like this in SQL.
UPDATE $table set column=value WHERE id=$ID
Hi brayan actually the problems is that no one will write code for you, you have to do it yourself. I guess you are unaware with SQL i.e., you asked that
how I configure this in the php script and mysql database to save the
information only once? because if the player with the id 50 play the
first level, will add a line with the information, but if the same
player play the first level again and change the amount of stars, I
dont want a new line, just update the stars amount.
Anyhow You first required some basic understanding of SQL and PHP with Unity. I will recommend you this Guide Server_Side_Highscores of unityWiki it help you to make database and server logic intergartion with PHP.
Now for your Second important part of question.
You have to update user code after each level completion.Or you can simply ask to user about socre save.
Before inserting new record into the database you have to check that userId with level id alread exist or not. some thing like this
Select userid, level, stars
from youTableName
where userid = ?
and level = ?
if the above query return empty response then you simply need to add the record
INSERT INTO table_name (userid, level, stars)
VALUES (value1,value2,value3);
Otherwise you have to update that specific column.

How to use Counter enabled loop processing of mysql query?

I have a database named creative_db Table name is store_weblinks. Now inside this table there are several columns that holds the entire site's weblinks. My focus is on this column - weblinks_status
Now weblinks_status contains 2 values = waiting and live
So here's what I intend to do - Update any 10 waiting to live status.
I think I need a loop of some kind that would keep a count of how many successful edits are taking place. Once that hits 10 it will stop processing..
So, its like this -- Check if the current item of weblinks_status is waiting. If it is waiting then change it to live and increment the loop counter else proceed to the next waiting item..
Need your help!
Try this STATEMENT:
UPDATE store_weblinks
SET weblinks_status="live"
WHERE weblinks_status="waiting"
LIMIT 10;
This should work to update any 10 entries from waiting to live.

Queries at the same time using MySQL

I'm designing a chat application using AJAX, PHP, MySQL and am having a problem.
MySQL table is like this username[varchar11]-taken[enum-0,1](coloumns) generally a user who wants to talk to userX, firstly checks if taken value of userx is 0 or 1
SELECT * FROM usertable WHERE username=userX
If it is 0 the user updates taken as 1:
UPDATE usertable SET taken=1 WHERE username=userX
and starts to talk to userX.
But when there are two users who want to talk to userX and check the taken value at the same time unconsciously, they both get 0 response. As a result of it, both of their updates are taken as 1 afterwards and start to talk to userX at the same time. Effectively, 3 users will be talking each other. This chat site is supposed to allow a user to talk to maximum one user at the same time. I'm trying to design it like that.
I'd appreciate any suggestions. What I need is something which is going to stop other users from checking and updating taken value until one finishes to check and update taken value.

How to update 100,000 record MySQL database efficiently

I have to update a 100,000 + MySQL database from PHP that pulls data from an API. It fails if I try and do more than 5,000 at the time.
I'm thinking the best approach might be to do 5,000 by using an update query with a limit 0, 5000 and then timestamping these records with the time they are updated. Then, select the next 5,000 where the time last updated is over 20 minues since current time.
Can anyone please offer any help on how to construct this query? Or is this approach not optimal?
So this is the solution I have gone with, rightly or wrongly it works. So to recap the problem, I have 100k rows, I need to loop through these and pass a userid to an API that returns a json feed.
I use the data returned to update each record. For some reason this fails either becasue of a timeout or server 500 error which I believe to be due to the API. So instead of selecting all 100k reords, I just select 5k (limit 0, 5000) and add a column called 'updated' and mark this as true once it has updated.
I keep doing this until all records are updated. When this happens I set the updated column to false and start the process again. This script runs on a chron job every 30 minutes and seems to work fine. I guess I could discover why it was timing out in the first place but I suspect it could be a php ini issue (timeout setting) which I don'thave access to.
Thanks
Jonathan
Create a temporary table, multi insert the update data and then
UPDATE `table`, `tmp`
SET `table`.`column` = `tmp`.`column`
WHERE `table`.`id` = `tmp`.`id`;

Categories