I have a DB-Application and now we have to start with replication (master-master-replication).
We build a stored-function which returns an BIGINT. This value is unique on all involved servers.
The situation:
I have a table definition:
create table test (
id BIGINT not null primary key auto_increment,
col1 TEXT);
the table has a before insert trigger:
CREATE TRIGGER test_insert BEFORE INSERT ON test
FOR EACH ROW BEGIN
IF NEW.id = 0 THEN
SET #my_uuid = MYUUID();
SET NEW.id = #my_uuid;
END IF;
END;
after an insert into test (col1) values ("foo") I need the value of the LAST_INSERT_ID() - but I only get the value "0".
I tried this in the trigger:
SET NEW.id = LAST_INSERT_ID(#my_uuid);
but it don´t work.
I read the mysql manpage which says, that all changes on last_insert_id within triggers and functions will be canceled at the end of the trigger.
So I try to avoid changing the application (which use php.last_insert_id())...
any ideas how to solve this without changing php-code?
greatings.
I assume that you're trying to avoid an insert on the two masters ending up with the same ID.
One way to do this (assuming 2 masters) is to set auto_increment_increment to 2, and auto_increment_offset to 0 on one master, and 1 on the other.
This will result in ids on each master that cannot collide with the other.
Aside: with a bigint and random UUIDs, you current approach is likely to have a collision somewhere around 3 billion rows due to the birthday paradox.
The behavior of LAST_INSERT_ID in mysql in relation to triggers is actually quite "un-standard" when compared to most other database server technologies. But I find it safer and much easier to work with.
Anyway, there's no one definite answer to you question, as replication is always complex. But the answer of user83591 is sufficient to give you a solution for most cases. Notion of the birthday paradox is also very much in place. You should accept that as your answer.
Related
I'm working on a system where I need to generate a six digit code for every user that signs up. So I'm using this statement (SELECT LEFT(CAST(RAND()*1000000000+999999 AS INT),6) for generating it. I have made that particular row UNIQUE. The thing is that this is all happening through a trigger. My question is, What happens if the number generated by this RAND() is already in use? Will the trigger be executed again as that particular is UNIQUE? or Do I need to write any condition in the trigger itself? If I need to write any condition, Please help me with it.
If the randomizer generates a value that has already been used, and stores it in a column that has a UNIQUE constraint, then the row will violate the constraint, and the INSERT and any other data changed by the trigger will be cancelled.
The trigger will not retry. A retry would need to be executed by your application code, after catching the error.
It would be far simpler to use a table's auto-increment mechanism to guarantee that values are not reused.
An example. Use with caution!!!
CREATE TRIGGER tr_bi_generate_pin
BEFORE INSERT
ON test
FOR EACH ROW
BEGIN
REPEAT
SET NEW.pin = CEIL(255 * RAND()); -- 255 is MAXVALUE for TINYINT UNSIGNED
SET NEW.iterations = NEW.iterations + 1;
UNTIL NOT EXISTS ( SELECT NULL
FROM test
WHERE pin = NEW.pin ) END REPEAT;
END
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=11c263a2eb07b8db133ae13a3d22e549
This code is relatively safe - it counts the amount of iterations, and if it reaches 256 the insertion will fail. But on real system, without such counting and with more wide datatype, the code may cause server hang because of too long, infinite-like, loop. So add maximal iterations amount checking - query fail is better than server hang.
Note: I'm new to databases and PHP
I have an order column that is set to auto increment and unique.
In my PHP script, I am using AJAX to get new data but the problem with that is, is that the order skips numbers and is substantially higher thus forcing me to manually update the numbers when the data is inserted. In this case I would end up changing 782 to 38.
$SQL = "INSERT IGNORE INTO `read`(`title`,`url`) VALUES\n ".implode( "\n,",array_reverse( $sql_values ) );
How can I get it to increment +1?
The default auto_increment behavior in MySQL 5.1 and later will "lose" auto-increment values if the INSERT fails. That is, it increments by 1 each time, but doesn't undo an increment if the INSERT fails. It's uncommon to lose ~750 values but not impossible (I consulted for a site that was skipping 1500 for every INSERT that succeeded).
You can change innodb_autoinc_lock_mode=0 to use MySQL 5.0 behavior and avoid losing values in some cases. See http://dev.mysql.com/doc/refman/5.1/en/innodb-auto-increment-handling.html for more details.
Another thing to check is the value of the auto_increment_increment config variable. It's 1 by default, but you may have changed this. Again, very uncommon to set it to something higher than 1 or 2, but possible.
I agree with other commenters, autoinc columns are intended to be unique, but not necessarily consecutive. You probably shouldn't worry about it so much unless you're advancing the autoinc value so rapidly that you could run out of the range of an INT (this has happened to me).
How exactly did you fix it skipping 1500 for ever insert?
The cause of the INSERT failing was that there was another column with a UNIQUE constraint on it, and the INSERT was trying to insert duplicate values in that column. Read the manual page I linked to for details on why this matters.
The fix was to do a SELECT first to check for existence of the value before attempting to INSERT it. This goes against common wisdom, which is to just try the INSERT and handle any duplicate key exception. But in this case, the side-effect of the failed INSERT caused an auto-inc value to be lost. Doing a SELECT first eliminated almost all such exceptions.
But you also have to handle a possible exception, even if you SELECT first. You still have a race condition.
You're right! innodb_autoinc_lock_mode=0 worked like a charm.
In your case, I would want to know why so many inserts are failing. I suspect that like many SQL developers, you aren't checking for success status after you do your INSERTs in your AJAX handler, so you never know that so many of them are failing.
They're probably still failing, you just aren't losing auto-inc id's as a side effect. You should really diagnose why so many fails occur. You could be either generating incomplete data, or running many more transactions than necessary.
After you change 782 in 38 you can reset the autoincrement with ALTER TABLE mytable AUTO_INCREMENT = 39. This way you continue at 39.
However, you should check why your gap is so high and change your design accordingly. Changing the autoincement should not be "default" behaviour.
I know the question has been answered already.. But if you have deleted rows in the table before, mysql will remember the used ID/Number because typically your Auto increment is Unique.. So therefore will not create duplicate increments.. To reindex and increment from the current max ID/integer you could perform:
ALTER TABLE TableName AUTO_INCREMENT=(SELECT max(order) + 1 FROM tablename)
auto increment doesn't care, if you delete some rows - everytime you insert a row, the value is incremented.
If you want a numbering without gaps, don't use auto increment and do it by yourself. You could use something like this to achive this for inserting
INSERT INTO tablename SET
`order` = (SELECT max(`order`) + 1 FROM (SELECT * from tablename) t),
...
and if you delete a row, you have to rearange the order column manually
I have a table tblsessions. At one time, only one session could be current as is session 2014-2015.
However, if i make 2015-2016 current, 2014-2015 should not be current anymore.
How could I implement this logic in table at design time?
Here is the table creation code waiting for your modification:
create table tblsessions(
sessionid int not null auto_increment,
sessionname varchar(9) not null,
current ????
primary key (sessionid)
);
You could perhaps use a trigger (depending on the version of MySQL you're running). I've assumed that current is a tinyint but you can adjust to whatever type you use:
CREATE TRIGGER curr_check BEFORE UPDATE ON tblsessions
FOR EACH ROW
BEGIN
IF NEW.current = 1 THEN
UPDATE tblsessions SET current = 0;
END IF;
END;
EDIT:
A.5.3: Does MySQL 5.6 have statement-level or row-level triggers?
In MySQL 5.6, all triggers are FOR EACH ROW—that is, the trigger is activated for each row that is inserted, updated, or deleted. MySQL 5.6 does not support triggers using FOR EACH STATEMENT.
ALTERNATIVE SOLUTION:
I have come up with another solution however I wonder if it really is a good solution.
I have created two tables:
TBLSESSIONS (session)
// session is primary key and stops duplicates
TBLCURRENTSESSION (csessionid, csession)
// csessionid is auto-int
// csession is foreign key to TBLSESSIONS.session
Each time user presses a button [Make This Session Default], I can insert that session into csession.
In code I can search for largest csessionid and find csession against it as the CURRENT SESSION.
This also allows user to switch sessions at time.
Being MySQL DBA, do you think it is a good approach to solving my basic problem? Do you see any dark sides of this solution?
I am looking to have the automagically set autoincrement included in another column during the insert. For example in a table where ID is the autoincrement and Name is the other column, I'd like to do something like
`INSERT INTO Names (Name) VALUES (CONCAT("I am number ",ID));`
Currently, I do the INSERT without Name, then I have to immediately after do an UPDATE using $mysqli->insert_id.
I don't want to query the table in advance because, as small a time as it may be, another record could get inserted between getting the next autoincrement value and the insertion. A trigger could work, but it seems like overkill. I just want to know if I can reference the autoincrement within the insertion.
Many thanks!
The problem is not as easy as it seems. In a BEFORE INSERT trigger, the autoincrement value hasn't been generated yet (NEW.autoinc_column is 0), and in an AFTER INSERT trigger, it is not possible anymore to change the values to be inserted.
With MyISAM tables, you could check the table definition for the next AUTO_INCREMENT value:
DELIMITER //
CREATE TRIGGER inserName BEFORE INSERT ON name FOR EACH ROW
BEGIN
DECLARE next_ai INT;
SELECT auto_increment INTO next_ai
FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'name';
SET NEW.name = CONCAT("I am number ", next_ai);
END //
DELIMITER ;
I believe this could work too with InnoDB tables if innodb_autoinc_lock_mode = 0 (not the case by default), but I am not sure (because of possible concurrency issues).
But if the value to concat is always the same, you probably had better using a view such as:
CREATE VIEW names_v AS SELECT id, CONCAT("I am number ", id) AS name FROM names;
I've also recently been facing this problem and although it might not be the best solution, what I did could be a viable alternative for your case. In my case it was sufficient. You could use a AFTER INSERT trigger to update the field with the value refering to the pseudo-variable NEW. This will probably give you a bit more flexibility.
I needed to fill a field with a string that was computed using the value from the auto increment column.
The trigger reads the NEW pseudo-variable, computes the necessary values and executes the UPDATE.
It still does require two high level write-acesses to the database, but all are done consecutively and without the need for further interaction with the client application (effectively, one single insert statement being sent from the client application, being followed by the implicit update).
I need to sync values in a table column in mysql trigger while having the same value in another column. Here is an example of my table:
id___MP____sweek
1____2_____1
2____2_____1
3____1_____2
4____1_____2
5____3_____3
6____3_____3
If a user changes, for example, MP in the first row (id=1) from 2 to 4, then the value of MP with the same sweek has to be changed (e.g., id=2, MP becomes also 4).
I wrote a BEFORE UPDATE tigger that does not work:
USE moodle;
DELIMITER $$
CREATE TRIGGER trigger_course_minpostUPD BEFORE UPDATE ON moodle.mdl_course_sections FOR EACH ROW
BEGIN
IF NEW.MP <> OLD.MP THEN
BEGIN
SET #A=NEW.MP;
SET NEW.MP = #A
WHERE OLD.sweek=NEW.sweek;
END;
END IF;
END$$
DELIMITER ;
From within a MySQL trigger you are not able to affect other rows on the same table.
You would want to say something like:
UPDATE my_table SET MP=NEW.MP WHERE sweek = NEW.sweek
But - sorry - no go.
There are hack around this -- and ugly ones, too.
If your table is MyISAM, you can wrap it up with a MERGE table, and act on the MERGE table instead (MySQL doesn't realize at that point you're actually hacking around it).
However, using MyISAM as a storage engine may not be a good thing -- today's focus is on InnoDB, a much more sophisticated engine.
Another trick is to try and use the FEDERATED engine. See relevant post by Roland Bouman. Again, this is a dirty hack.
I would probably let the application do the thing within the same transaction.