provide read and write table locking in mysql - mysql

I have a table
CREATE TABLE `uli` (
`id` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
The table shall always contain one value in one row. But it could be changed often. I would like to make a select, lock the table for other connections, update a value and the unlock.
For example, the following code works excellent:
LOCK TABLES uli WRITE;
SELECT * FROM uli;
UPDATE uli SET id=id+1;
UNLOCK TABLES;
While first connection do not unlock, all other connection will wait, and only after unlock could see new value. That is exactly what I want. Is it exists some more elegant solution? Does it matter MyIsam or Innodb table to use?

You are locking for write, which means selects will still see the old value before the update. Also you are not doing anything with your select. If you use InnoDB and SERIALIZABLE isolation level, all you need is the update statement.

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 insert on duplicate key related deadlock

In logs I have deadlocks that are related to this query:
INSERT INTO `driver_state` (id, state)
VALUES('83799','waiting')
ON DUPLICATE KEY UPDATE
state = IF(state = 'active', state, VALUES(state));
Exact error:
ER_LOCK_DEADLOCK: Deadlock found when trying to get lock; try restarting transaction
I have tried to reason and understand how this query could even possibly cause a deadlock, but haven't gotten very far.
Table structure
CREATE TABLE IF NOT EXISTS `driver_state` (
`id` int(11) NOT NULL,
`state` enum('inactive','waiting_orders','has_order','busy') DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MEMORY DEFAULT CHARSET=utf8;
In this case we use MEMORY table, but with InnoDB it has same locking issues.
Table gets lots of these insert on duplicate key queries and select queries (All select queries are very fast and optimized). Transactions are not used anywhere nor manual locking etc.
Could you please propose any ideas what could possibly cause this?
Why is there an IF statement in your ON DUPLICATE KEY UPDATE?
I think your problem is there. It looks like you are saying if there is a duplicate key, set the value of state depending on what the value of state is.
What is wrong with ON DUPLICATE KEY UPDATE state='waiting'?
I would try this to see if the deadlock goes away to confirm that it is caused by the IF.
I would also explain here in greater detail what you are trying to do.

Write in SQL statement

I am reading a sql.txt file and this is one section. What it does is create a table "Page":
DROP TABLE IF EXISTS `Page`;
CREATE TABLE `Page` (
`id` bigint(20) NOT NULL auto_increment,
`pageId` int(11) default NULL,
`name` varchar(255) default NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `pageId` (`pageId`)
)
--
-- Dumping data for table `Page`
--
LOCK TABLES `Page` WRITE;
/*!40000 ALTER TABLE `Page` DISABLE KEYS */;
/*!40000 ALTER TABLE `Page` ENABLE KEYS */;
UNLOCK TABLES;
I have a few questions:
Is the TABLES a database name?
Does the WRITE load data to the table 'page'? If yes, where is the source of the data?
Does the lock mean first locking the database?
TABLES is not the database name. It means which table(s) to lock (in this case table 'Page').
WRITE means, the table is locked for writing into it.
It means locking the table, not the database.
LOCK TABLES is a SQL command (see link for the documentation) that will apply a lock to the set of tables specified. In this case, it is only one table, Page:
LOCK TABLES `Page` WRITE;
In this case it would be two tables:
LOCK TABLES `Page`, `Book` WRITE;
The lock can have a 'type', which in this case is WRITE. That means no data can be written to the table while this lock is in place. This ensures that no updates overwrite what is being written before everything has been completed. This keeps the database table in a known state.
Note that you can still read from a table under a write lock. This means that reads will be nondeterministic as the data is being written.
SQL doesn't support locking the entire database (as far as I know). The above command will only lock the specified tables.

Why INSERT IGNORE increments the auto_increment primary key?

I wrote a java program that accesses a MySQL innodb database.
Whenever an INSERT IGNORE statement encounters a duplicate entry the Auto Increment primary key is incremented.
Is this behaviour the expected? I think it shouldn't happen with IGNORE. That means that IGNORE actually incurs an extra overhead for writing the new primary key value.
The table is the following:
CREATE TABLE `tablename` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`rowname` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `rowname` (`rowname`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Thank you!
This has been the default behaviour since MySQL 5.1.22.
You can set the configuration variable innodb_autoinc_lock_mode to 0 (a.k.a “traditional” lock mode) If you'd like to avoid gaps in your auto-increment columns. It may incur a performance penalty, though, as this mode has the effect of holding a table lock until the INSERT completes.
From the docs on InnoDB AUTO_INCREMENT Lock Modes:
innodb_autoinc_lock_mode = 0 (“traditional” lock mode)
The traditional lock mode provides the same behavior that existed
before the innodb_autoinc_lock_mode configuration parameter was
introduced in MySQL 5.1. The traditional lock mode option is provided
for backward compatibility, performance testing, and working around
issues with “mixed-mode inserts”, due to possible differences in
semantics.
In this lock mode, all “INSERT-like” statements obtain a special
table-level AUTO-INC lock for inserts into tables with AUTO_INCREMENT
columns. This lock is normally held to the end of the statement (not
to the end of the transaction) to ensure that auto-increment values
are assigned in a predictable and repeatable order for a given
sequence of INSERT statements, and to ensure that auto-increment
values assigned by any given statement are consecutive.
I believe this is a configurable setting in InnoDB. See: AUTO_INCREMENT Handling in InnoDB
You'd want to go with
innodb_autoinc_lock_mode = 0
INSERT INTO `tablename` (id, rowname) SELECT '1', 'abc' FROM dual WHERE NOT EXISTS(SELECT NULL FROM `tablename` WHERE `rowname`='abc');
or short (because the id field has an increment in the table )
INSERT INTO `tablename` (rowname) SELECT 'abc' FROM dual WHERE NOT EXISTS(SELECT NULL FROM `tablename` WHERE `rowname`='abc');
The solution may look cumbersome, but it works as the author needs.
I think this behaviour is reasonable. The auto-increment should not be relied upon to give sequences that don't have gaps.
For example, a rolled back transaction still consumes IDs:
INSERT INTO t (normalcol, uniquecol) VALUES
('hello','uni1'),
('hello','uni2'),
('hello','uni1');
Generates a unique key violation obviously, and inserts no rows into the database (assuming transactional engine here). However, it may consume up to 3 auto-inc values without inserting anything.
Not sure if it's expected, though I would recommend switching to:
INSERT ... ON DUPLICATE KEY UPDATE