I have table with fields:
id
data1
data2
next I want to set trigger that after update would write to log table changes:
CREATE TRIGGER `update_data` AFTER UPDATE on `data_table`
FOR EACH ROW
BEGIN
IF (NEW.data1 != OLD.data1) THEN
INSERT INTO data_tracking set old_value = OLD.data1, new_value = NEW.data1, field = "data1";
END IF;
-- similar for data2
END$$
I also want to record in data_tracking table user that made change, however this user is not part of original UPDATE that trigger the trigger. I it a way to let trigger know what user need to be recorded ?
This is PHP based web service with multiple registered users, that can make changes to record via website - those user i would like to add to trigger.
As you want to use the user name that only is known to PHP, well, MySql cannot know which user triggered the change if this information is only available in PHP. This means you would at least have to pass the user in every update statement, either adding this as a column to all tables that need this kind of trigger, or do all the updates via stored procedures that get the user name as an additional parameter. Then you could get rid of all the triggers, as you would use stored procedures anyway that can do the logging as well as the updates.
Use CURRENT_USER, see the MySQL manual: http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_current-user
Related
All,
I am using MySql 5.7 in my application. I trying to make my save function Concurrency Safe. I will explain with an example.
Example :
I have two admin users Admin 1 and Admin 2. We have a product table and we have a product table entry with product code "P1". Suppose Admin 1 and Admin 2 are logged into the system and try to update product entry with code "P1" at the same time.
I need to inform one of the users that the record(s) you are trying modify is updating by another user and try again after some time.
I am using transaction and didn't change MySql's default transaction level(repeatable read). I am trying to solve it by using "SELECT FOR UPDATE"(included a where condition to check with modified time). This "where" condition will solve concurrency issue to those transactions which are already committed. But if two transaction starts at the same time and the first transaction gets committed before lock timeout, then when the second transaction executes, it overwrites the first one.
Kindly share your ideas
Thanks in advance
Well there are actually 2 issues here.
First, one of the admins will get a lock on the row before the other, so assuming admin1 gets the lock first, admin2 will queue until admin1's transaction completes, then admin2's transaction will take place.
So that is all looked after for you by the DBMS.
But the second issue is of course if both admin1 and admin2 are attempting to update the same column(s). In this case admin1's update will be overwritten by admin2's update. The only way to stop this happening if that is what you want to stop is to make the UPDATE very specific about what it is updating. In other words the UPDATE must be something like this
UPDATE table SET col1 = 'NewValue'
WHERE usual criteria
AND col1 = 'Its Original Value'
So this means that when you present the original data from this row to the user in a form, you must somehow remember what its original state was as well as capture its new state that the admin changed it to.
Of course the PHP code will also have to be written to capture the fact the UPDATE did not take place and return something to whichever admin's update has now failed. Showing the new value in the column in question and giving them a notice that the update failed because someone else already changed that field, and letting them either forget there change, or apply their change over the top of the other admins update.
There is this technique you can use to control it without locking the table or controlling the update you should do.
Create a field on your table that will be a version for that registry:
alter table someTable add column version not null integer default 0;
There will be no need to change any insert code with this.
Every time a user fetches a registry to update you make sure that it will have the version also in the object (or form,m or whatever way you handle your entity in the system).
Then you will need to Create a before update trigger for your table that will check if the version of the current registry is still the same is so you update if not you raise an error. Something like:
delimiter $$
create trigger trg_check_version BEFORE UPDATE
ON yourTable
for each row
begin
if NEW.version != OLD.version then
signal sqlstate '45000' set message_text = 'Field outdated';
else
NEW.version = NEW.version + 1;
end if;
end$$
delimiter;
Then you handle the error in your php code. This signal command will only work on MySql 5.5 or later. Check out this thread
I need to allow edits made via a DB GUI though to my auditing system so that they are also being tracked. I am updating the audit table using a BEFORE trigger, and i would like to be able to alter the audit row to indicate what the source of the update was (via an existing 'modified_by' column).
I cannot add an additional column to the audit table for this because I want to be able to copy from the updated table to the audit table without specifying table columns like below (I don't want this trigger to require that I update it with changes to the schema).
MY TRIGGER:
BEGIN
INSERT INTO contacts_audit SELECT * FROM contacts WHERE id = NEW.id;
END
PSEUDO TRIGGER:
BEGIN
IF(GUI EDIT) THEN
SET NEW.modified_by_id = 1; /* 1 is the administrator id */
END IF;
INSERT INTO contacts_audit SELECT * FROM contacts WHERE id = NEW.id;
END
A "DB GUI" connecting to the database is a client, a php connection is a client too, the same as any other way to connect to the database, so there is no flag like "this is php".
You can however use the USER() or SESSION_USER()-function in the trigger to get the active user/hostname of the current connection, so just use a different user/host for php and the gui if you want to seperate them.
Is there a way in MySQL and PHP to allow only the person performing an update to view information about a particular record?
For example, if one user loads the page they are presented with a record which must be updated, until that user finishes updating this record, any other user accessing this page should not be able to view this particular record.
You'll have to manually lock the row (by adding a IsLocked column) when someone requests the edit page, since the connection to the database is lost as soon as the PHP script execution ends (despite pooling et al, script execution is stopped so you cannot unlock from the same thread again since that connection may go to another script).
Don't forget to create a kind of unlocking script, initiated by cron for example, to unlock rows that have been locked for more than a given amount of time.
I don't recommend adding any column to data table in question. I'd rather create special locks table to hold the information:
create table locks (
tablename varchar,
primarykey int,
userid int,
locktime datetime);
Then take the following principles into consideration:
each PHP request is a standalone mysql connection, that's why solutions like SELECT ... FOR UPDATE won't work and that's why you need to keep userid of a person - who actually did first request and performed the lock
every access to locks table must lock the table as a whole (using MySQL's LOCK statement) to avoid concurrency in locking the same row
if there is no way to know, whether particular uses has abandoned editing the row by closing the window with record - then either locktime timeout must be short or you should provide some ping (i.e. AJAX) mechanism that would reset locktime as long as user is working on locked record
user can save changes to record as long as he/she owns the lock and locktime did not expire
tablename and primarykey are of course samples and you should adjust them to your needs :-)
add a "locked" column to the table, and once a user calls the edit form, set the "locked" db value to the user_id, and after save set it back to false/null.
In your view action, check the locked value
You can either add a field named like locked where you set a status. Maybe you also add a field like lockedtime where you save a timestamp how long the lock is active. That depends on your needs.
There are also possibilities to do this native. Like
SELECT * FROM table WHERE primarykey = x FOR UPDATE;
Let's say I have two tables as shown:
user
id plan course_limit username
10 0 ahmad
note: plan is enum field containing '','a','b','c'.
course
id user_id username
1 10 ahmad
Now I want when a user insert into course as shown I want the course_limit to increment by 1 for that user so that I can apply a limit.
You can create a trigger with the following code.
CREATE
DEFINER = 'root'#'localhost'
TRIGGER databasename.AI_course_each
AFTER INSERT
ON databasename.course
FOR EACH ROW
BEGIN
UPDATE user SET user.course_limit = user.course_limit + 1
WHERE user.user_id = new.user_id;
END;
Some explanation
You create a trigger that fires once for every row FOR EACH ROW that is inserted AFTER INSERT into table course.
When you insert into the table you can trigger BEFORE and AFTER the insert is done.
If you want to be able to prevent the insert you fire before, if you just want to do useful work, you fire after.
The inserted fields can be accessed via a dummy table 'new'.
So here after each insert the UPDATE statement gets executed.
More trigger options
You can also use triggers BEFORE UPDATE, AFTER UPDATE,BEFORE DELETE and AFTER DELETE.
In the update and delete cases you get an extra dummy table old that you can use to refer to the data in the table before the update or delete happened.
Lots more is possible, but I'll keep it simple for now.
See http://forge.mysql.com/wiki/Triggers for more info.
Using a trigger should solve your problem : with that, you'll be able to register SQL code, on your MySQL server, that runs when certain events occur (like insertion).
I have a mysql database with 12,000 entries, what i want setup is the ability to monitor a column in the database and if/when the column is altered for any entry it sends me an email with the details.
EDIT: I have access to mysql db, but not the script which works with it. So it should monitor it for changes...
You could create some triggers on the table, if your version of MySQL has them. A trigger can then invoke any function you care to create. A trigger has the advantage that any insertion or deletion or any update of the column will cause it to fire; you wouldn't have to change any other code to make it happen. See here for more... http://dev.mysql.com/doc/refman/5.0/en/triggers.html
Create a trigger on update
Create another table (lets call it cron_table), where the trigger will insert information of the updated row (may be old value, new value etc)
Setup a cron, which will call a script which will check the cron_table and send email if any entry is found. Cron interval can be setup according to need.
--- If you could send email from trigger, there would be no need for a separate table and cron ---
try something similar to this , you can edit the function to send you and email if the query has insert and TABLE_NAME or COLUMN_NAME in it
set up one column to be a datetimestamp.
This will update on every change of the row. There you can run a sql query either via a cron job or after every few php queries to return you the list of changed rows since the last check.
Select * from tbl_myentries where EntryUpdated > '$TimeSinceLastCheck'
you need to understand Data Manipulation Language (DML) triggers
in my sql: use
CREATE TRIGGER salary_trigger
BEFORE UPDATE ON table_name
REFERENCING NEW ROW AS n, OLD ROW AS o
FOR EACH ROW
IF n.columnName <> o.columnname THEN
END IF;
;
Create a trigger on a change on your column, then insert it to another table as log table.
Run cron job on your table that will send you an email.