InnoDB Foreign Keys and Commit - mysql

I'm converting an existing database from MyISAM to InnoDB and implementing various foreign keys, I'm having an issue with running the convert script on my database though:-
I'm running all queries as below
DELETE FROM example WHERE user NOT IN (select id FROM users);
ALTER TABLE `example` CHANGE `user` `user` INT( 11 ) UNSIGNED NOT NULL ;
ALTER TABLE example ADD FOREIGN KEY (user) REFERENCES users(ID);
ALTER TABLE example ADD FOREIGN KEY (car) REFERENCES cars(ID);
When I run all queries it fails due to a foreign key constraint, due to the fact the DELETE statement hasn't run - if I run them individually, it's fine - is it an issue with commit on the innodb database or is it due to speed of the delete not completing before the next query?
Is it also ok to have two foreignkeys of ID? (two different tables users.id and cars.id).
Thanks!

No idea of what the error message might say or what you're trying to accomplish but ALTER TABLE is a DDL statement and those cannot be rollbacked in MySQL. The Statements That Cause an Implicit Commi manual chapter explains:
The statements listed in this section (and any synonyms for them)
implicitly end any transaction active in the current session, as if you had done a COMMIT before executing the statement. As of MySQL
5.5.3, most of these statements also cause an implicit commit after executing;
[...]
Data definition language (DDL) statements that define or modify database objects
[...]
ALTER TABLE, CREATE TABLE, and DROP TABLE do not commit a transaction
if the TEMPORARY keyword is used. (This does not apply to other
operations on temporary tables such as CREATE INDEX, which do cause a
commit.) However, although no implicit commit occurs, neither can the
statement be rolled back. Therefore, use of such statements will
violate transaction atomicity: For example, if you use CREATE
TEMPORARY TABLE and then roll back the transaction, the table remains
in existence.

Related

MySQL InnoDB: locking the destination of foreign keys

When I add a row that references another table (in a transaction), MySQL seems to lock the whole row that's being referenced. This prevents updates of other columns in the destination table that should be able to run concurrently without any problem.
Simplified example:
CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`num` int(11) DEFAULT NULL,
UNIQUE KEY `id` (`id`)
);
CREATE TABLE `bar` (
`foo` int(11) NOT NULL,
KEY `foo` (`foo`),
CONSTRAINT `bar_ibfk_1` FOREIGN KEY (`foo`) REFERENCES `t1` (`id`)
);
INSERT INTO `t1` VALUES (1,1),(2,0),(3,4);
Task A:
BEGIN;
insert into bar(foo) values(2);
-- now we ask task B to do some work for us
Task B:
-- when triggered by Task A, tries to do this:
update t1 set num=num+1 where id=2;
-- does not complete because it waits for the lock
Any ideas how to avoid this deadlock? Task A should only read-lock the single value which it actually refers to, so Task B couldn't renumber or delete t1[id=2].id but would otherwise be free to update that row. Is it possible to convince MySQL to do this?
Splitting t1 into two linked tables (one for Task A to refer to and one for task B to update) would result in a heap of fairly intrusive refactoring.
Joining the tasks is not an option because B's work changes global state, thus must be able to commit even if A fails.
Switching to Postgres (which supports this; I checked) is not an easily-executed option.
This is a behavior of MySQL foreign keys that frankly convinces many projects to avoid using foreign key constraints, even though their database logically has foreign key references.
You can't lock just one column of a row. InnoDB effectively locks the whole row against update or delete if an exclusive lock exists on a child row that references it. The idea is that while a child row is depending on that parent row and is in progress of an insert/update/delete, the parent row shouldn't be deleted or its key modified. But you can't lock only the key column that is referenced by the child row.
The best solution is for the transaction against the child table to be finished and committed promptly. The fact that you tried to update the parent row and it timed out (a lock wait timeout is 50 seconds by default) indicates that you have left the transaction running too long.
P.S. What you described is simply a lock-wait. That's not a deadlock. A deadlock is when both transactions end up blocked, waiting for each other to release locks but neither can proceed because they are both waiting. A lock-wait is unidirectional. A deadlock is a cycle of mutual lock-waits.

custom AUTO INCREMENT value not working

I have the following sql code to create a table
CREATE TABLE db.object (
`objid` bigint(20) NOT NULL AUTO_INCREMENT,
`object_type` varchar(32) NOT NULL,
PRIMARY KEY (`objid`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
However, the values in the objid are coming out as 1,2,3... (The insert statement is not adding the ids)
Shouldn't AUTO_INCREMENT=2 make the objid start from 2 instead of 1
With InnoDB tables, the AUTO_INCREMENT value will be reset to the maximum value (plus 1) when the table is opened. The auto increment value exists only in memory, it is not persisted on disk.
A table open would happen, for example, when the MySQL instance was shutdown and then restarted, and a reference is made to the table.
A table can also be closed at other times. For example, when open_table_cache is exceeded (that is, when a large number of other tables is opened), MySQL will close some of the open tables, to make room in the cache for newly opened tables.
I believe this behavior is documented somewhere in the MySQL Reference Manual.
I used your SQL, created the object table and entered two values for object_type and objid started at 2. Can't see anything wrong here...
It might. There are enough exceptions and gotchas with auto-inc on InnoDB tables that it bears urging a full review of the documentation.
That said, there is one scenario I can think of where MySQL ignores the initializer value. I'll quote the documentation:
InnoDB uses the in-memory auto-increment counter as long as the server runs. When the server is stopped and restarted, InnoDB reinitializes the counter for each table for the first INSERT to the table, as described [here]:
InnoDB executes the equivalent of the following statement on the first insert into a table containing an AUTO_INCREMENT column after a restart:
SELECT MAX(ai_col) FROM table_name FOR UPDATE;
A server restart also cancels the effect of the AUTO_INCREMENT = N table option in CREATE TABLE and ALTER TABLE statements, which you can use with InnoDB tables to set the initial counter value or alter the current counter value.
So if you create that table, then do a server restart (like as part of a deployment process), you'll get a nice value of 1 for the initial row. If you want to countermand this, you need to create the table, then insert a dummy row with the auto-inc value you want, then restart, then delete the dummy row.

mysql alter table will lock table?

When I alter MySQL table, add new column for my log table. specific statement like below:
alter table log_xxx add column `new_column` smallint NOT NULL after post_date;
Will above statement will lock table log_xxx if I execute SQL to insert some records when I execute this alter statement?
I make some test, execute insert statement do not include new column successfully while the alter statement still under processing. So I have no idea about the alter table add column will lock table or not?
Is there anyone who knows about this problem?
It depends on the storage engine, if you use InnoDB, it may alter table in place and not block the DML operation, and if then MyISAM is used, the update and write operations that start after alter begins, will be blocked until the new table is ready.
Those links maybe help:
https://dev.mysql.com/doc/refman/5.7/en/alter-table.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-create-index-overview.html

mariadb alter table lock strategy

I am using MariaDB 10.1.9. Short version: What I really want to know for certain is if I can modify an indexed auto_increment field on an innodb table from int to bigint without locking the table?
Long version: Is there a detailed explanation of which ALTER TABLE operations require which lock level? The documentation just says "Depending on the required operation and the used storage engine, different lock strategies can be used for ALTER TABLE.". It doesn't provide a link to any details and each operation on the ALTER TABLE page does not specify it's required level.
From experimentation, I know ADD COLUMN does not require a lock. MODIFY COLUMN allows reads, but can it be manually set to allow writes? The MariaDB documentation says you can set the lock level, but if you don't set it restrictive enough, it will give an error - but it doesn't say what that error is. The current table column definition looks like
`Id` int(10) NOT NULL AUTO_INCREMENT
KEY `Id` (`Id`)
When I try
ALTER TABLE MyTable MODIFY MyField bigint AUTO_INCREMENT LOCK=NONE;
I just get a generic SQL syntax error. Even if I specify DEFAULT, I get an error, so I'm not sure how to use the LOCK - which I would expect the proper error to tell me when I have chosen an improper lock level.
The syntax...
alter_specification [, alter_specification] ...
... requires a comma
ALTER TABLE MyTable
MODIFY COLUMN MyField BIGINT AUTO_INCREMENT, -- comma here
LOCK=NONE;
I'm guessing the error was not all that "generic" -- it should have said something about the right syntax to use near 'LOCK... which is your hint not that the quoted term is the beginning of the error, but rather that that the parser/lexer expected something other than the quoted value to occur at that position (because it was looking for the comma).
If the column you are altering is the primary key, a lock seems inevitable -- because the entire table should need rebuilding, including all the indexes, since the primary key "rides free" in all indexes, as it is what's used after a non-covering index lookup to actually find the rows matched by the index.

can't add foreign key in mysql?

I used MySQL workbench to add a foreign key in a table, but some strange error happened, this is the SQL statement:
ALTER TABLE `tansung`.`Declaration` ADD COLUMN `goodsId` INT(11) NOT NULL AFTER `declarationId` ,
ADD CONSTRAINT `goodsId`
FOREIGN KEY (`goodsId` )
REFERENCES `tansung`.`Goods` (`goodsId` )
ON DELETE NO ACTION
ON UPDATE NO ACTION
, ADD INDEX `goodsId` (`goodsId` ASC) ;
When i click apply, the surprise comes out!
ERROR 1005: Can't create table 'tansung.#sql-1b10_1' (errno: 150)
SQL Statement:
ALTER TABLE `tansung`.`Declaration` ADD COLUMN `goodsId` INT(11) NOT NULL AFTER `declarationId` ,
ADD CONSTRAINT `goodsId`
FOREIGN KEY (`goodsId` )
REFERENCES `tansung`.`Goods` (`goodsId` )
ON DELETE NO ACTION
ON UPDATE NO ACTION
, ADD INDEX `goodsId` (`goodsId` ASC)
ERROR: Error when running failback script. Details follow.
ERROR 1050: Table 'Declaration' already exists
SQL Statement:
CREATE TABLE `Declaration` (
`declarationId` int(11) NOT NULL,
PRIMARY KEY (`declarationId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
I can't find out any mistake in logic, even can't understand the error, please give me a help.
All foreign key names throughout the database must be unique. If you already have a foreign key named 'goodsId', even on another table, you will receive this error.
If the related columns do not have exactly the same type (e.g. INT) and constraints (UNIQUE and such), you will receive that error.
It can happen because of many reasons. Following are some of the common reasons. You can also say syntactical errors, because of which these kinds of error are thrown.
If the FK (Foreign Key) table Engine is using MyISAM and PK (Primary Key) table Engine is using InnoDB. MyISAM does not support foreign key constraints. So, you might want to converting your linking table to InnoDB.
All foreign key names throughout the database must be unique. If you already have a foreign key constraint with the same name, even on another table, you will receive this error.
If the related columns do not have exactly the same data typetype (e.g. INT) and constraints (UNIQUE and such), you will receive that error.
I'm getting this error when the table being linked to (in your case, Goods) is stored using MyISAM, and the table you're adding the index to (in your case, Declarations) is stored using InnoDB.
You can tell this from the files in the database directory. MyISAM tables will have files like:
table_name.frm
table_name.MYD
table_name.MYI
The InnoDB table will just have:
table_name.frm
MyISAM does not support foreign key constraints. I would suggest converting your Goods table to InnoDB (though, have a look at the documentation first and do some basic research):
ALTER TABLE Goods ENGINE=INNODB;
After making this change, my ADD INDEX operation completed successfully.
Like the others have said, first make sure the types of the two columns are the same and the database supports it. After that, make sure that the columns that hold the keys to the other tables are valid.
I had a problem where I was adding the constraint to an existing column with data in it, and that data didn't match any of the primary keys in the other table so the attempt to create the relationship would fail. Fixing it involved updating all the columns to make sure my column data matched up with the constraint I was trying to make.
I discovered that when trying to do this in phpMyAdmin that tables that had a hyphen in the name would only allow one FK and then give errors. I have no idea why but it was easy enough to work around I simply remade the
CREATE TABLE `something_new` LIKE `something-old`;
DROP TABLE `something-old`;
YMMV.
The type definitions of Goods.goodsId and Declarations.goodsId must be identical, or you will get the errno: 150.
Make sure they are both the same data type, which looks to be goodsId INT(11) NOT NULL in the Declarations table. What is the CREATE TABLE statement for Goods?
I had the same problem. It seems that there was some data in the child table that was not present in the parent table. You can do an outer join to see the differences and you can assign a valid id for non-matching rows or delete them:
DELETE FROM books
WHERE NOT EXISTS (
SELECT * FROM users
WHERE books.user_id = users.id
)
Errno 150 has a lot of causes. If you have SUPER privileges, you should try using
SHOW ENGINE INNODB STATUS
and that will tell you what the cause was. If you don't have SUPER privileges, you need to just go through all the possible causes. You can find how to use the SHOW INNODB STATUS and a list of all the causes here:
MySQL Foreign Key Errors and Errno 150
When I got that error it was becuase I was trying to update a table that already had data int it and the data didn't meet the FK restrictions.
A fourth possible problem (to the three proposed by abhijitcaps) is that you didn't make the column you are referencing to a primary key.