Strange behaviour from MYSQL 5 (Database Isolation) - mysql

I opened two command windows to work with my Database (MySQL5).
Below is table structure I'm working with (It should be noted that I've turned off the auto commit by executing set autocommit=0;):
Table Structure:
CREATE TABLE `ajax`.`zipcodes` (
`ZIPCODE` varchar(5) NOT NULL,
`CITY` varchar(50) DEFAULT NULL,
`STATE` varchar(2) DEFAULT NULL,
PRIMARY KEY (`ZIPCODE`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Below is sequence of activities:
Step 1:
In command window 1, I executed below commands and you could also see the output:
mysql> insert into ajax.zipcodes values(5, 'Wil', 'AK');
Query OK, 1 row affected (0.00 sec)
Step 2
In second command window, I fired below command and it hangs (it seems waiting for commit command to be issues from previous window)
mysql> update ajax.zipcodes set city='Dublin' where zipcode=5;
Step 3
I went to Command window#1, and executed commit; you could see the output below:
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
At the same time, i could see that second window that was earlier hanging, also executed the command and printed below output:
mysql> update ajax.zipcodes set city='Dublin' where zipcode=5;
Query OK, 1 row affected (3.63 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Step 4
Now I issues commit in my second window to ensure that all the changes gets commited properly even the second session:
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
Step 5
Now since commit has been issued from both windows I thought that everything is all right and the two session also must be in sync, so I went to 1st command window and issues below command:
mysql> select * from zipcodes where zipcode=5;
+---------+------+-------+
| ZIPCODE | CITY | STATE |
+---------+------+-------+
| 5 | Wil | AK |
+---------+------+-------+
1 row in set (0.00 sec)
I was surprised because I was expecting City value to be 'Dublin' because changes from second command window (i.e. update) has been commited in Step 4, but i'm still getting Wil in City column.
What am I doing wrong here?

This is to do with isolation levels. If you raise your isolation level to SERIALIZABLE (the default in MySQL is REPEATABLE READS) you won't get "phantom reads".
Isolation levels and phantom reads are described on the Wikipedia page for database transaction isolation.
If I run this through as you did, but with the higher isolation level, I get the result you were expecting.
Session 1
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `ajax`.`zipcodes` (
-> `ZIPCODE` varchar(5) NOT NULL,
-> `CITY` varchar(50) DEFAULT NULL,
-> `STATE` varchar(2) DEFAULT NULL,
-> PRIMARY KEY (`ZIPCODE`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.07 sec)
mysql> insert into ajax.zipcodes values(5, 'Wil', 'AK');
Query OK, 1 row affected (0.00 sec)
Session 2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)
mysql> update ajax.zipcodes set city='Dublin' where zipcode=5;
Session 1
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
Session 2
/* continued from previous (was frozen) */
Query OK, 1 row affected (7.54 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
Session 1
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
NB: This doesn't necessarily mean that you should always be using SERIALIZABLE - there are trade-offs. Most notable is that the database will acquire a range lock when executing a SELECT and you'll get more locking-based conflicts.
Update - Explicitly Handling Transactions
Since we have autocommit=0; set in these scripts, we really should handle the transactions explicitly, rather than expecting a START TRANSACTION - although in most cases, the database behaves as you'd expect if you'd executed START TRANSACTION.
However, run the original example while explicitly starting and ending all transactions (including those that are just SELECT, and you get a different result:
Session 1
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `ajax`.`zipcodes` (
-> `ZIPCODE` varchar(5) NOT NULL,
-> `CITY` varchar(50) DEFAULT NULL,
-> `STATE` varchar(2) DEFAULT NULL,
-> PRIMARY KEY (`ZIPCODE`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.07 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into ajax.zipcodes values(5, 'Wil', 'AK');
Query OK, 1 row affected (0.00 sec)
Session 2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> update ajax.zipcodes set city='Dublin' where zipcode=5;
Session 1
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
Session 2
/* continued from previous (was frozen) */
Query OK, 1 row affected (8.32 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
Session 1
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)

Related

MySQL DEFAULT vs. MariaDB DEFAULT

I have table_a with and auto_increment column named id and string column named name.
Running the statement:
INSERT INTO table_a(id, name)VALUES(DEFAULT, 'test');
Results to (MySQL):
+----+------+
| id | name |
+----+------|
| 1 | test |
+----+------+
Running the similar statement in MariaDB results to:
+----+------+
| id | name |
+----+------|
| 0 | test |
+----+------+
Other scenario:
I tried editing the AUTO_INCREMENT value of the table to 30. MySQL inserts 30 while MariaDB inserts 0.
What is the difference of DEFAULT value in INSERT statement of MySQL and MariaDB? Is this a bug in MariaDB or it is working as intended?
This behavior is controlled by SQL_MODE='NO_AUTO_VALUE_ON_ZERO', both in MySQL and MariaDB. If you observe the difference, it's most likely because you have different sql_mode on the instances.
MariaDB [test]> CREATE TABLE t (id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY);
Query OK, 0 rows affected (0.20 sec)
MariaDB [test]> SET SQL_MODE='';
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> INSERT INTO t (id) VALUES (DEFAULT);
Query OK, 1 row affected (0.05 sec)
MariaDB [test]> SELECT * FROM t;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
MariaDB [test]> DROP TABLE t;
Query OK, 0 rows affected (0.14 sec)
MariaDB [test]> CREATE TABLE t (id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY);
Query OK, 0 rows affected (0.30 sec)
MariaDB [test]> SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO';
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> INSERT INTO t (id) VALUES (DEFAULT);
Query OK, 1 row affected (0.03 sec)
MariaDB [test]> SELECT * FROM t;
+----+
| id |
+----+
| 0 |
+----+
1 row in set (0.00 sec)

MySql innoDB auto increment lock workaround

I have a project in which I insert a lot if info to a table with an auto increment primary key per second and do that with multi-threading which means that there are many threads that tries to insert a new row to that table. Because there is a lock on the table for insert queries, I cant perform inserts concurrently and therefore I cant get maximum performance from the threads...
Is there a way to overcome this lock?
You can set the innodb_autoinc_lock_mode to 1 in the my.cnf. Then there is no LOCK for Auto_increment. Then it is possible that you have holes in the Values if one Thead rollback an see sample
MariaDB [test]> show variables like 'innodb_autoinc_lock_mode';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_autoinc_lock_mode | 1 |
+--------------------------+-------+
1 row in set (0.00 sec)
sample 1
MariaDB [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> insert into autoinc VALUES(NULL,'hello');
Query OK, 1 row affected (0.01 sec)
thread 2 ------------------> MariaDB [test]> start transaction;
thread 2 ------------------> Query OK, 0 rows affected (0.00 sec)
thread 2 ------------------> MariaDB [test]> insert into autoinc VALUES(NULL,'world');
thread 2 ------------------> Query OK, 1 row affected (0.00 sec)
thread 2 ------------------> MariaDB [test]> commit;
thread 2 ------------------> Query OK, 0 rows affected (0.00 sec)
thread 2 ------------------> MariaDB [test]>
MariaDB [test]> commit;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> select * from autoinc;
+----+-------+
| id | d |
+----+-------+
| 1 | hello |
| 2 | world |
+----+-------+
2 rows in set (0.00 sec)
MariaDB [test]>
sample 2 with rollback
MariaDB [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> insert into autoinc VALUES(NULL,'Guten');
Query OK, 1 row affected (0.00 sec)
thread 2 ------------------> MariaDB [test]> start transaction;
thread 2 ------------------> Query OK, 0 rows affected (0.00 sec)
thread 2 ------------------> MariaDB [test]> insert into autoinc VALUES(NULL,'Tag');
thread 2 ------------------> Query OK, 1 row affected (0.00 sec)
thread 2 ------------------> MariaDB [test]> commit;
thread 2 ------------------> Query OK, 0 rows affected (0.01 sec)
MariaDB [test]> rollback;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> select * from autoinc;
+----+-------+
| id | d |
+----+-------+
| 1 | hello |
| 2 | world |
| 4 | Tag |
+----+-------+
3 rows in set (0.00 sec)
MariaDB [test]>

FLUSH TABLES don't work .

WHy Flush Tables don't worK ? I can INSERT /SELECT into TABLE.
mysql> FLUSH TABLES;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT COUNT(*) FROM big_table;
+----------+
| COUNT(*) |
+----------+
| 1054155 |
+----------+
1 row in set (1.13 sec)
mysql> INSERT INTO exept VALUES(1);
Query OK, 1 row affected (0.02 sec)
I have all privileges.
When I use FLUSH TABLES WITH READ LOCK I can't insert but can select queries:
mysql> FLUSH TABLES WITH READ LOCK;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO exept VALUES(1);
ERROR 1223 (HY000): Can't execute the query because you have a conflicting read lock
mysql> SELECT * FROM exept;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.03 sec)
How disabled INSERT/SELECT queries ?

mysql delete triggers

I think I read that the delete trigger doesn't know what data was deleted and loops over the whole table applying the trigger. Is that true?
Does that mean that the before delete loops over the whole table before the data is deleted and after delete loops over the whole table after the delete occurs?
Is there no way to loop over just the deleted records? So If 10 records are deleted loop over them?
DELIMITER $$
DROP TRIGGER `before_delete_jecki_triggername`$$
CREATE TRIGGER before_delete_triggername
BEFORE DELETE ON table
FOR EACH ROW
BEGIN
/*do stuff*/
END$$
DELIMITER ;
Thanks,
Mat
I think it was due to a confusion with FOR EACH ROW statement.
It is only for "matched records for the statement issued before trigger is invoked."
If there exists N number of records in a table and matches records for where id=x,
assuming x causes a result of less than N records, say N-5, then
FOR EACH ROW causes a loop for N-5 times only.
UPDATE:
A sample test run on the rows affected due to FOR EACH ROW statement is shown below.
mysql> -- create a test table
mysql> drop table if exists tbl; create table tbl ( i int, v varchar(10) );
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.06 sec)
mysql> -- set test data
mysql> insert into tbl values(1,'one'),(2,'two' ),(3,'three'),(10,'ten'),(11,'eleven');
Query OK, 5 rows affected (0.02 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> select * from tbl;
+------+--------+
| i | v |
+------+--------+
| 1 | one |
| 2 | two |
| 3 | three |
| 10 | ten |
| 11 | eleven |
+------+--------+
5 rows in set (0.02 sec)
mysql> select count(*) row_count from tbl;
+-----------+
| row_count |
+-----------+
| 5 |
+-----------+
1 row in set (0.00 sec)
mysql>
mysql> -- record loop count of trigger in a table
mysql> drop table if exists rows_affected; create table rows_affected( i int );
Query OK, 0 rows affected (0.02 sec)
Query OK, 0 rows affected (0.05 sec)
mysql> select count(*) 'rows_affected' from rows_affected;
+---------------+
| rows_affected |
+---------------+
| 0 |
+---------------+
1 row in set (0.00 sec)
mysql>
mysql> set #cnt=0;
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> -- drop trigger if exists trig_bef_del_on_tbl;
mysql> delimiter //
mysql> create trigger trig_bef_del_on_tbl before delete on tbl
-> for each row begin
-> set #cnt = if(#cnt is null, 1, (#cnt+1));
->
-> /* for cross checking save loop count */
-> insert into rows_affected values ( #cnt );
-> end;
-> //
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> delimiter ;
mysql>
mysql> -- now let us test the delete operation
mysql> delete from tbl where i like '%1%';
Query OK, 3 rows affected (0.02 sec)
mysql>
mysql> -- now let us see what the loop count was
mysql> select #cnt as 'cnt';
+------+
| cnt |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
mysql>
mysql> -- now let us see the table data
mysql> select * from tbl;
+------+-------+
| i | v |
+------+-------+
| 2 | two |
| 3 | three |
+------+-------+
2 rows in set (0.00 sec)
mysql> select count(*) row_count from tbl;
+-----------+
| row_count |
+-----------+
| 2 |
+-----------+
1 row in set (0.00 sec)
mysql> select count(*) 'rows_affected' from rows_affected;
+---------------+
| rows_affected |
+---------------+
| 3 |
+---------------+
1 row in set (0.00 sec)
mysql>

mysql update row based on field value

How can I update a field in mysql based on the previous value?
Lets say that count equals 3.
UPDATE MY_TABLE SET count = count-1
Will that work? so the new value of count would be 4
Did you mean for the row or the table? If you mean on insert or update for a row you can use triggers.
I gave it a try and it worked, so perhaps you need to show us the CREATE statement for the table. Test results:
mysql> create table aint ( count int not null default 0 );
Query OK, 0 rows affected (0.25 sec)
mysql> insert into aint VALUES(1);
Query OK, 1 row affected (0.06 sec)
mysql> select * from aint;
+-------+
| count |
+-------+
| 1 |
+-------+
1 row in set (0.00 sec)
mysql> update aint set count = count-1;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from aint;
+-------+
| count |
+-------+
| 0 |
+-------+
1 row in set (0.00 sec)
mysql> update aint set count = count-1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from aint;
+-------+
| count |
+-------+
| -1 |
+-------+
1 row in set (0.00 sec)