I have to decrease money from a user account and increase another user account, namely to transfer money from an account to another.
I have this code for example, in MySql:
START TRANSACTION;
UPDATE accounts
SET balance = (balance-100)
WHERE account_id = 2 AND balance>100;
--If the above query is succesfully then:
UPDATE accounts
SET balance = (balance+100)
WHERE account_id =1;
--How can I exec the commit only if everything is ok?
COMMIT;
The first query is executed only if the balance>100.
However the second query (namely the second update) should be executed only if the prevoious query has decreased the balance. How could I automatically check this?
Furthermore the COMMIT; has to be executed only if the previous 2 queries have done their job.
How could this be implemented?
(I'm using PHP too but I think this problem could easily tackled using sql. Am I wrong?)
Perform the operation as single query, not as a query pack:
UPDATE accounts t1
CROSS JOIN accounts t2
SET t1.balance = (t1.balance-100),
t2.balance = (t2.balance+100)
WHERE t1.account_id = 2 AND t1.balance>100
AND t2.balance_id = 1;
-- or
UPDATE accounts
SET balance = balance + CASE account_id WHEN 1 THEN 100
WHEN 2 THEN -100 END
WHERE account_id IN (1,2);
And you do not need in transaction at all.
Also you may check the amount of rows altered (by fact, on disk, not formally) by previous query, and take this info into account in 2nd query:
START TRANSACTION;
UPDATE accounts
SET balance = (balance-100)
WHERE account_id = 2 AND balance>100;
UPDATE accounts
SET balance = (balance+100)
WHERE account_id =1
AND ROW_COUNT(); -- check does a row was altered in previous statement
-- if not then this statement will not alter any row too
COMMIT;
Related
i don't familiar at database, That is my test syntax:
START TRANSACTION ;
SET #VAR = (SELECT `some ID` FROM `some table` ORDER BY `some ID` DESC LIMIT 1);
SELECT #VAR;
COMMIT;
SELECT #VAR;
i think is result is first select is null (because before commit) and second select is have value, and in my test first and second select have value, why? and how to fix my syntax?
You seem confused. First, changes made within a transaction are visible within the same transaction. Second, transactions are about changes to the database, not changes to the session. After all, the database is ACID-compliant (or not), not the variables in a session.
The first print prints the value present during the transaction. Changes within a transaction are visible -- in the transaction. This is true for changes on tables, as well. If you insert a row in a table and -- in the same transaction -- look for the row, then you will see it.
You should not see the row in another session. You won't see it elsewhere, until the changes are committed.
So, I have a table A that each time a user sends an image, a record is created storing the time it was uploaded, the username of the user and the image number out of all the images uploaded over time.
I need to make a second table B that will store the amount of images uploaded per user and the user name. I need this table B to be updated when a new entry is generated in A.
I found that a trigger function can be created, nevertheless I'm having a rough time finding an example that will suit my needs.
Does anyone know a way of doin what I want?
Just update b table with a select count of total inserted records on a from current user NEW.userid (userid is your column name or whatever name you have there, and NEW is a fixed mySql reference for the current values to be inserted):
CREATE TRIGGER img_sum AFTER INSERT ON a
FOR EACH ROW SET b.total = (SELECT COUNT(*) FROM a WHERE a.userid=NEW.userid)
WHERE b.userid = NEW.userid;
From what you have described i don't think you need a second table. You can just count the number of time a user name has occurred, and you will get the number of images that user has uploaded.
You can get the count doing something like that
SELECT COUNT(DISTINCT username) FROM table_name;
If you still need to create 2 tables, you might want to take a look at procedures and how they work.
Let's say we have 3 tables for this case:
- users(id, username, email ....),
- user_images(id, userId, image_num, date_uploaded)
- user_images_count(id, user_name, images_count)
The user_images_count is initially empty. We have to fill it up by such query:
INSERT into user_images_count(user_name, images_count)
SELECT (select username from users where ui.userId = id) as username, count(userId) as counter FROM `user_images` ui group by ui.userId;
Then, we must immediately create the trigger that will process every INSERT operation into user_images table.
CREATE TRIGGER `count_user_images` AFTER INSERT ON `user_images`
FOR EACH ROW begin
declare u_name tinytext default "";
set u_name = (select username from users where id = NEW.userId limit 1);
if(u_name != "") then
update user_images_count set images_count = images_count + 1 where user_name = u_name;
end if;
end
This two queries (user_images_count fulfillment and trigger creation must be performed in one transaction, one by one).
I've created similar triggers on my local databases. They work pretty good. )))
I am working on an Online Booking System. In a normal process when a user selects a room which was marked as 'Available' at a particular time and submits his choice. Now at back-end, just to confirm, I run a SELECT statement to verify the room's status is available or not as follows:
SELECT room_status FROM rooms WHERE room_number = 'XXX'
then I run a UPDATE query to mark it as 'UNAVAILABLE':
UPDATE rooms SET room_status = 'UNAVAILABLE' WHERE room_number = 'XXX' AND room_status = 'AVAILABLE'
Now when I ran SELECT query, the room was available and as I proceed to UPDATE query, the room is taken by other user. So the UPDATE query will fail. I want the room to be taken by first user only. What should I do ?
UPDATE: There can also be a case when a user has to book multiple rooms. For instance user selects 8 rooms. My code was at 4th room checking its status and as it reached 8th room the first room is gone. Also it may happen, while I was checking 4th room and upto now 8th room is gone.
I am using : PHP and MySQL
When selecting the rows, you need to lock them, so that other queries can't read or write from them until you finish updating them. In MySQL, this can be done inside a transaction using the SELECT ... FOR UPDATE statement:
START TRANSACTION;
SELECT room_status FROM rooms WHERE room_number = 'XXX' FOR UPDATE;
All the rows returned from the previous statement will be locked until the end of the current transaction. You can now mark them as unavailable:
UPDATE rooms SET room_status = 'UNAVAILABLE' WHERE room_number = 'XXX';
Remember to close the transaction by commiting the changes. This will release the lock on the rows.
COMMIT;
In this situation, submit the UPDATE first:
UPDATE rooms
SET room_status = 'UNAVAILABLE'
WHERE room_number = 'XXX' AND room_status = 'AVAILABLE'
Then check the number of affected rows. If the number of affected rows is 1, then you successfully booked the room.
If anyone else submits the exact same query, only one will succeed.
I find that on Insert, Update and Delete you need to add with NC (no commit) to the end of the statement, otherwise it doesn't work.
That will hopefully fix it.
I have table, and many (too many) requests for selecting from it a single row. After selecting a row, the script run update query to set a flag that is that row had been "selected". But as we have too many requests per time, in period between one thread select a row, and update its flag, another thread have time to select the same row.
Select query get one row from the table, ordering it by some field and using LIMIT 0, 1. I need that DB just skip the row, that had been selected before.
The engine is InnoDB.
Just before you start a transaction, call the following:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
This will ensure that if you read a row with a flag, it'll still be that way when you update it within the same transaction.
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT id_site
INTO #site
FROM table1 WHERE flag = 0 ORDER BY field LIMIT 0,1;
UPDATE table1 SET flag = 1 WHERE id_site = #site;
COMMIT;
I have a lot of entries in a table that are fetched for performing jobs. this is scaled to several servers.
when a server fetches a bunch of rows to add to its own job queue they should be "locked" so that no other server fetches them.
when the update is performed a timestamp is increased and they are "unlocked".
i currently do this by updating a field that is called "jobserver" in the table that defaults to null with the id of the jobserver.
a job server only selects rows where the field is null.
when all rows are processed their timestamp is updated and finally the job field set to null again.
so i need to synchronize this:
$jobs = mysql_query("
SELECT itemId
FROM items
WHERE
jobserver IS NULL
AND
DATE_ADD(updated_at, INTERVAL 1 DAY) < NOW()
LIMIT 100
");
mysql_query("UPDATE items SET jobserver = 'current_job_server' WHERE itemId IN (".join(',',mysql_fetch_assoc($jobs)).")");
// do the update process in foreach loop
// update updated_at for each item and set jobserver to null
every server executes the above in an infinite loop. if no fields are returned, everything is up 2 date (last update is not longer ago than 24 hours) and is sent to 10 minutes.
I currently have MyIsam and i would like to stay with it because it had far better performance than innodb in my case, but i heard that innodb has ACID transactions.
So i could execute the select and update as one. but how would that look and work?
the problem is that i cannot afford to lock the table or something because other processes neeed to read/write and cannot be locked.
I am also open to a higher level solution like a shared semaphore etc. the problem is the synchronization needs to be across several servers.
is the approach generally sane? would you do it differently?
how can i synchronize the job selectino to ensure that two servers dont update the same rows?
You can run the UPDATE first but with the WHERE and LIMIT that you had on the SELECT. You then SELECT the rows that have the jobserver field set to your server.
If you can't afford to lock the tables, then I would make the update conditional on the row not being modified. Something like:
$timestamp = mysql_query("SELECT DATE_SUB(NOW(), INTERVAL 1 DAY)");
$jobs = mysql_query("
SELECT itemId
FROM items
WHERE
jobserver IS NULL
AND
updated_at < ".$timestamp."
LIMIT 100
");
// Update only those which haven't been updated in the meantime
mysql_query("UPDATE items SET jobserver = 'current_job_server' WHERE itemId IN (".join(',',mysql_fetch_assoc($jobs)).") AND updated_at < ".$timestamp);
// Now get a list of jobs which were updated
$actual_jobs_to_do = mysql_query("
SELECT itemId
FROM items
WHERE jobserver = 'current_job_server'
");
// Continue processing, with the actual list of jobs
You could even combine the select and update queries, like this:
mysql_query("
UPDATE items
SET jobserver = 'current_job_server'
WHERE jobserver IS NULL
AND updated_at < ".$timestamp."
LIMIT 100
");