Updates in mysql transaction - isolation - php

I have following model
Inventory [product_name, quantity, reserved_quantity]
with data
[Shirt, 1, 0]
[Shorts, 10, 0]
What happens if following code is executed in multiple threads at the same time?
$changes = [
['name' => 'Shirt', 'qty' => 1],
['name' => 'Shorts', 'qty' => 1],
];
$db->startTransaction();
foreach($changes as $change){
$rowsUpdated = $db->exec("UPDATE inventory
SET reserved_quantity = reserved_quantity + $change['qty']
WHERE product_name = $change['name']
and quantity >= reserved_quantity + $change['qty']");
if($rowsUpdated !== 1)
$db->rollback();
exit;
}
$db->commit();
Is it possible that the result will be?
[Shirt, 1, 2]
[Shorts, 10, 2]

It's not.
Lets see what would be happening in the following scenario:
The first transaction starts
UPDATE Shirt => an exclusive row lock will be set on the record
The second transaction starts
The second transaction tries to UPDATE Shirt. As it would need to obtain a record lock it would wait as this record has already been locked by the first transaction
The first transaction commits, the second one would resume execution and see the updated record
Of course it's only relevant to InnoDb and similar mysql engines.
Please note that you're lucky enough that traversing the records in the same order. If it were not the case you might run into a deadlock

Related

update multi rows in laravel by eloquent

Suppose we have one dataBase that have one table called fathers; and another table called children.
I want get all children whose father is mamali.
$pls = children::where(['father_id' => 5, 'isGoodBoy' => true])->take(4)->get();
And i want change $pls and set father_id to 7,8,50,55. so can do this one by one request in foreach :
for ($i = 0; $i < count($pls); $i++) {
$pls[$i] = $arayWhoWantBaby[$i];
$pls[$i]->save();
}
This work but do with many request...(in this example 1 request for get and 4 request for update ! )
I want do this with one or two DB request,one to get data from DB and another set new data with one request to do all work and update items $pls[0][1][2]...
one thing as "in" keyword in sql for update ;
This is what I meant. Honestly, I'd just stick with the extra 4 queries. Small updates like that shouldn't be an issue.
Also, doing it like this will not trigger any Eloquent events.
$father_ids = [7, 8, 50, 55];
$children_ids = children::where(['father_id' => 5, 'isGoodBoy' => true])->take(4)->pluck('id')->all();
$sql = <<<SQL
UPDATE
children
SET
father_id = CASE id
WHEN :children_id_1 THEN :father_id_1
WHEN :children_id_2 THEN :father_id_2
WHEN :children_id_3 THEN :father_id_3
WHEN :children_id_4 THEN :father_id_4
END
WHERE
id IN (:children_id_1, :children_id_2, :children_id_3, :children_id_4)
SQL;
$bindings = [
'children_id_1' => $children_ids[0],
'children_id_2' => $children_ids[1],
'children_id_3' => $children_ids[2],
'children_id_4' => $children_ids[3],
'father_id_1' => $father_ids[0],
'father_id_2' => $father_ids[1],
'father_id_3' => $father_ids[2],
'father_id_4' => $father_ids[3],
];
DB::update($sql, $bindings);

Getting bulk update to work in laravel 5.6

My app is being made in laravel 5.6
Situation:
I have a table called "members" with a column called "membershipstatus_id".
options for status are 4, 5 and 1
4 = Active, 5 = pending and 1= expired
Target:
I want to update all active(4) members to pending(5) and all pending ones to expire(1).
Solutions I have tried:
So far, below is what i have tried with no result.
// get all active and pending members
$members = Member::where('membershipstatus_id', 5)
->orWhere('membershipstatus_id', 4)
->get();
// bulk update with chunk of 200, if this is possible
$members->chunk(200, function($members)
{
foreach($members as $member)
{
// if status is pending, update to expire
if($member->membershipstatus_id == 5)
{
$member->update(['membershipstatus_id' => 1]);
}
// if status is active, update to pending, i updated a small mistake here.
if($member->membershipstatus_id == 5)
{
$member->update(['membershipstatus_id' => 4]);
}
}
}
);
return "update confirm";
Now, If anyone has a cleaner and swift way to do this, Please let me know, also, Im sure i have made some stupid mistake up there. Please point me to the right direction.
Ashish
Use the query builder like:
// update pending to expired
DB::table('members')
->where('membershipstatus_id', 5)
->update(['membershipstatus_id' => 1]);
// update active to pending
DB::table('members')
->where('membershipstatus_id', 4)
->update(['membershipstatus_id' => 5]);

Increment columns in laravel

Is there a way to increment more than one column in laravel?
Let's say:
DB::table('my_table')
->where('rowID', 1)
->increment('column1', 2)
->increment('column2', 10)
->increment('column3', 13)
->increment('column4', 5);
But this results to:
Call to a member function increment() on integer
I just want to find an efficient way to do this using the given functions from laravel. Thanks. Any suggestions will do.
There is no existing function to do this. You have to use update():
DB::table('my_table')
->where('rowID', 1)
->update([
'column1' => DB::raw('column1 + 2'),
'column2' => DB::raw('column2 + 10'),
'column3' => DB::raw('column3 + 13'),
'column4' => DB::raw('column4 + 5'),
]);
Increments and Decrements in Laravel Eloquent Model
Add to cart option is one of the most important functions in e-commerce websites. The tricky part is getting the number of items in the cart to display on the cart icon. The predominant approach to get this done is using the increment and decrement function on Laravel. This also facilitates the addition or removal of a product from your cart. The way to implement this function is ,
$user = User::find(‘517c43667db388101e00000f’);
$user->cart_count++;
// $user->cart_count--; // for decrement the count
$user->save()
An alternate and easier way is,
$user = User::find($article_id);
$user->increment('cart_count');
Also these will work:
$user->increment('cart_count');// increase one count
$user->decrement('cart_count'); // decrease one count
$user->increment('cart_count',10); // increase 10 count
$user->decrement('cart_count',10); // decrease 10 count
Now in laravel 5.7 laravel query builder, increment and decrement, it can be done easily.
Model::where('id', "rowID")->increment('columne1');`
or you can use DB
DB::table("my_table")->where('id', "rowID")->increment('column1');
For future reference in 5.2 it has been made do able by doing the following
You may also specify additional columns to update during the operation:
DB::table('users')->increment('votes', 1, ['name' => 'John']);
Source: https://laravel.com/docs/5.2/queries#updates
First off, the result of increment is an integer according to the documentation: http://laravel.com/api/4.2/Illuminate/Database/Query/Builder.html
So you would have to do a call for each increment:
DB::table('my_table')
->where('rowID', 1)
->increment('column1', 2);
DB::table('my_table')
->where('rowID', 1)
->increment('column2', 10);
DB::table('my_table')
->where('rowID', 1)
->increment('column3', 13);
DB::table('my_table')
->where('rowID', 1)
->increment('column4', 5);
I'm unable to find any quicker solution, unless you want to solve it with a raw update query command.
Also your example code will probably generate an error as you've closed the statement with ; and continue with a new ->increment call on the next line.
$id=5;
$votes=20;
DB::table('users')
->where('id', $id)
->update([
'votes' => DB::raw('votes + '.$votes)
]);
just use this code
\App\Models\User::find(1)->increment('column1'); //id = 1
or multi recorded
\App\Models\User::where('column1','>','100')->increment('column1');
In the latest version of 9.x, you can increment and decrement multiple columns using incrementEach and decrementEach methods:
DB::table('users')->incrementEach([
'votes' => 5,
'balance' => 100,
]);
(taken from the documentation)
Model::where('id', "rowID")->increment('columne1');
This works for me

php/mysql transaction not rolling back on failure (Codeigniter framework)

below I have the following code. If the transaction fails, it is NOT being rolled back. If I remove the lock table statements it rolls back. Is there anything special I need to do in order to use locks and transactions?
function save ($items,$customer_id,$employee_id,$comment,$show_comment_on_receipt,$payments,$sale_id=false, $suspended = 0, $cc_ref_no = '', $auth_code = '', $change_sale_date=false,$balance=0, $store_account_payment = 0)
{
if(count($items)==0)
return -1;
$sales_data = array(
'customer_id'=> $this->Customer->exists($customer_id) ? $customer_id : null,
'employee_id'=>$employee_id,
'payment_type'=>$payment_types,
'comment'=>$comment,
'show_comment_on_receipt'=> $show_comment_on_receipt ? $show_comment_on_receipt : 0,
'suspended'=>$suspended,
'deleted' => 0,
'deleted_by' => NULL,
'cc_ref_no' => $cc_ref_no,
'auth_code' => $auth_code,
'location_id' => $this->Employee->get_logged_in_employee_current_location_id(),
'store_account_payment' => $store_account_payment,
);
$this->db->trans_start();
//Lock tables invovled in sale transaction so we don't have deadlock
$this->db->query('LOCK TABLES '.$this->db->dbprefix('customers').' WRITE, '.$this->db->dbprefix('sales').' WRITE,
'.$this->db->dbprefix('store_accounts').' WRITE, '.$this->db->dbprefix('sales_payments').' WRITE, '.$this->db->dbprefix('sales_items').' WRITE,
'.$this->db->dbprefix('giftcards').' WRITE, '.$this->db->dbprefix('location_items').' WRITE,
'.$this->db->dbprefix('inventory').' WRITE, '.$this->db->dbprefix('sales_items_taxes').' WRITE,
'.$this->db->dbprefix('sales_item_kits').' WRITE, '.$this->db->dbprefix('sales_item_kits_taxes').' WRITE,'.$this->db->dbprefix('people').' READ,'.$this->db->dbprefix('items').' READ
,'.$this->db->dbprefix('employees_locations').' READ,'.$this->db->dbprefix('locations').' READ, '.$this->db->dbprefix('items_tier_prices').' READ
, '.$this->db->dbprefix('location_items_tier_prices').' READ, '.$this->db->dbprefix('items_taxes').' READ, '.$this->db->dbprefix('item_kits').' READ
, '.$this->db->dbprefix('location_item_kits').' READ, '.$this->db->dbprefix('item_kit_items').' READ, '.$this->db->dbprefix('employees').' READ , '.$this->db->dbprefix('item_kits_tier_prices').' READ
, '.$this->db->dbprefix('location_item_kits_tier_prices').' READ, '.$this->db->dbprefix('location_items_taxes').' READ
, '.$this->db->dbprefix('location_item_kits_taxes'). ' READ, '.$this->db->dbprefix('item_kits_taxes'). ' READ');
$this->db->insert('sales',$sales_data);
$sale_id = $this->db->insert_id();
//A bunch of mysql other queries to save a sale
$this->db->query('UNLOCK TABLES');
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE)
{
return -1;
}
return $sale_id;
}
I believe it's mainly beacuse of the fact that MySQL "LOCK TABLES" commits any active transaction before attempting to lock the tables.
Actually, the interaction between the two actions (LOCKING and TRANSACTIONS) looks quite tricky in MySQL, as it's very clearly and completely outlined in the MySQL manual page.
The proposed solution is to turn OFF the autocommit flag (which is ON by default, so usually every query issued is automatically committed after execution) by issueing the SET autocommit = 0 command:
The correct way to use LOCK TABLES and UNLOCK TABLES with
transactional tables [..] is to begin a transaction with SET
autocommit = 0 (not START TRANSACTION) followed by LOCK TABLES, and to
not call UNLOCK TABLES until you commit the transaction explicitly.
For example, if you need to write to table t1 and read from table t2,
you can do this:
Your code could look like this:
$this->db->query("SET autocommit=0");
$this->db->query("LOCK TABLES...."); // your query here
$this->db->query("COMMIT");
$this->db->query("UNLOCK TABLES');

Save inside loop

I created a function that has a infinite loop and a delay at the end to make sure it will only execute one time per second.
This function is in a shell class and its goal is to save/update records in three different tables and I invoque it from the console line using the command 'cake className'
My problem is:
I stop the loop at the console (Ctrl + C) and only the last record is saved in the database.
I don't know if there is some problems with transaction, i tried to use begin() before save and commit after, but the problem subsisted.
The code is something like this:
$this->T1->begin();
$this->T2->begin();
$this->T3->begin();
if ($this->T1->save((array (
'Field1' => $val1,
'Field2' => $val2,
'Field3' => $val3)))
&& $this->T2->save(array (
'Field1' => $val4,
'Field2' => $val5,
'Field3' => $val6)))
&& $this->T3->saveField('Field1', $val7))
{
$this->T1->commit();
$this->T2->commit();
$this->T3->commit();
echo 'success message';
}
It could be because the id is still present in each of the models which often happens when saving in a loop because the data gets merged.
Try the following to reset the models so they don't load in the previous data.
$this->Sale->create(false);
$this->Bidagent->create(false);
$this->Licitation->create(false);
From your code snippet though i'm not sure what T1, T2 and T3 are... if they are models then they need the same $this->Model->create(false);
Reference:
http://api.cakephp.org/2.3/class-Model.html#_create

Categories