SELECT and UPDATE statements one after other - php

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.

Related

How to check if previous query was executed correctly?

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;

Scheduled spesific time queries

I have a table which is i showed on picture below.
Flight Table
MYSQL TABLE
[Id], [flightCode], [TailId], [DepartureFrom], [DepartureDate],
[ArrivedTo], [ArrivalDate], [Status]
VALUES
[*1], [TK161], [TC-AAS], [DLM], [04.03.2021 15:00], [IST], [04.03.2021
16:02], [2]
I want to run mysql query for update Status from "2" to "3" after reach to ArrivalDate in real system time.
Which method I should to use?
What you really want is to have status be a computed column or query and compute that column on the fly. https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html doing continuous updates across a database is VERY processor intensive and unnecessary.
so instead of SELECT * FROM TABLE you could do
SELECT *,
CASE WHEN NOW() >= ArrivalDate THEN 3 ELSE 2 END AS `Status`
FROM TABLE
This query will give you the correct status at query time. So the work to compute it only happens when it is needed. I assume that there are other conditions for status but you can nest them into the case or have an outer case for those

How to block table in booking?

How to block table to booking? My booking table has from_datetime and to_datetime fields. I would like to avoid situation, when two users book something in the same time.
My attempts:
TERMINAL 1: - user1
LOCK TABLE booking READ;
INSERT INTO booking VALUES ('2019-01-01 12:00:00', '2019-01-01 14:00:00');
Now table booking should be locked, and other users should not be able to read from its, but...
TERMINAL 2: - user2
SELECT * from booking
I have results... Why? This should be blocked until I do in TERMINAL 1:
unlock tables;
You need to use LOCK TABLE booking WRITE;. That indicates that you're going to be writing to the table, and you need to block everyone else from reading and writing the table.
A READ lock means that you're only going to read the table. It blocks other users from writing to the table, so that all your reads access consistent table contents. But it doesn't block other users from reading the table.
However, if you're using InnoDB you should use transactions rather than table locks. This is more fine-grained, and figures out what to lock automatically. Your code would look something like this:
START TRANSACTION;
SELECT COUNT(*) FROM booking
WHERE from_datetime = '2019-01-01 12:00:00' AND to_datetime = '2019-01-01 14:00:00'
FOR UPDATE;
-- If this returns zero count:
INSERT INTO booking VALUES ('2019-01-01 12:00:00', '2019-01-01 14:00:00');
COMMIT;
The FOR UPDATE option in the SELECT causes MySQL to lock the appropriate part of the table. If another user executes the same code, they'll be blocked if they try to select the same times.
Locking tables is not such a good idea in some cases.
You should use a Transaction and check in your transaction if the ranges overlap.
Check overlap of date ranges in MySQL
https://dev.mysql.com/doc/refman/8.0/en/commit.html

Preventing inserting same value twice in an oracle database

I have a number which is in this form : 2012-01 (2012 as current year) and 01 is just a the maximum value of a field in my database incremented by 1, and each year that number is reset to 0.
but if there are two users that try to do the same operation at the same time the value is the same for both and thus i get the same number inserted twice in my database .
I thought of creating a sequence but that requires a job that resets the sequence each year and i would prefer if there is a way to make a lock before i get the next number and
release it after an insert is done ?
Thanks.
CREATE UNIQUE INDEX index_name ON table_name (column_name);
or
ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE (column_name);
You don't specify where you store the field that is used as the counter. But maybe it is possible to use a SELECT FOR UPDATE statement.
Before you increment the value of your counter field by 1 you can lock that record by using a SELECT FOR UPDATE. Then update the counter.
Something like this, assuming the table has only 1 record:
SELECT *
FROM CounterTable
FOR UPDATE;
UPDATE CounterTable
SET Counter = Counter + 1;
COMMIT;
If one session (user) has done the SELECT FOR UPDATE and not yet committed or rolled back, the other session (user) doing a SELECT FOR UPDATE will block waiting to be able to get a lock. This prevents two users from getting the same number.

Mysql lock concurrent read/update of row

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;

Categories