简介:mysql
在默认的状况下,MySQL在自动提交(autocommit=1/ON)模式运行,这种模式会在每条语句执行完毕后把它做出的修改马上提交给数据库并使之永久化。事实上,这至关于把每一条语句都隐含地当作一个事务来执行。若是你想明确地执行事务,须要禁用自动提交模式并告诉MySQL你想让它在什么时候提交或回滚有关的修改。sql
咱们想要明确的执行事务的话,用START TRANSACTION(或BEGIN)开启事务,其实它会自动挂起自动提交事务模式(即会忽略autocommit=1),而后执行本次事务的各语句,最后用COMMIT语句结束事务并把它们作出的修改永久性记入数据库。万一事务过程当中发生错误,用一条ROLLBACK语句撤销事务并把数据库恢复到事务开始以前的状态。数据库
START TRANSACTION语句在COMMIT/ROLLBACK以后会作什么?bash
答:在事务被提交或回滚以后,该模式将恢复到开始本次事务的START TRANSACTION语句被执行以前的状态,这里有两种状况:1.若是自动提交模式原来是激活的,结束事务将让你回到自动提交模式session
2.若是它原来是非自动提交模式的,结束当前事务将开始下一个事务并发
下面实例说明mvc
假若有表 t(a, b, c)主键为a,b为惟一索引,c为常规字段测试
查看是否自动提交 mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 可见以上为自动提交 mysql> select * from t; +---+------+------+ | a | b | c | +---+------+------+ | 1 | 1 | 1 | | 2 | 2 | 2 | +---+------+------+ 2 rows in set (0.00 sec)
如下全为自动模式下作的实验:spa
链接1 | 链接2 |
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 2 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 2 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> update t set c=3 where b=2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 3 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 3 | +---+------+------+ 1 row in set (0.00 sec) 可见别的链接已看到链接1的更改 |
链接1 | 链接2 |
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 3 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 3 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> update t set c=4 where b=2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 4 | +---+------+------+ 1 row in set (0.00 sec) 可见自身事务下c已为4 |
|
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 3 | +---+------+------+ 1 row in set (0.00 sec) 可见链接2里c还为3,由于链接1里的事务未提交 |
|
mysql> commit; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 4 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 4 | +---+------+------+ 1 row in set (0.00 sec) 可见在链接1里的事务提交后,这里看到了最新的c |
链接1 | 链接2 |
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 4 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 4 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> update t set c=5 where b=2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
mysql> update t set c=6 where b=2; .... 此处会被挂起等待,由于链接1里的事务已在b=2的 这条记录上给加锁了 此处有两种状况: 1. 锁等待超时那么会报错 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 2. 未超时的状况,在链接1里的事务执行完后,这个继续执行 此处以未超时的状况走,在链接1里的事务commit后, 此处会自动提交update |
|
mysql> commit; Query OK, 0 rows affected (0.00 sec) |
|
Query OK, 0 rows affected (3.97 sec) Rows matched: 1 Changed: 0 Warnings: 0 久违的提示信息呢。 |
|
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 5 | +---+------+------+ 1 row in set (0.00 sec) 因为链接2里的事务未提交,此处查询的还是5 |
|
mysql> commit; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 6 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 6 | +---+------+------+ 1 row in set (0.00 sec) 可见已变 |
链接1 | 链接2 |
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 6 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 6 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> update t set c=c+1 where b=2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 7 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> update t set c=c-2 where b=2; ... 此处会挂起等待 |
|
mysql> commit; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 7 | +---+------+------+ 1 row in set (0.00 sec) |
|
Query OK, 1 row affected (21.60 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 5 | +---+------+------+ 1 row in set (0.00 sec) 可见这里减2,已取到链接1里的事务的更改, 刚才的挂起等待就是为了数据的惟一性 |
|
mysql> commit; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 5 | +---+------+------+ 1 row in set (0.00 sec) |
|
mysql> select * from t where b=2; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 5 | +---+------+------+ 1 row in set (0.00 sec) 可见已为5,原值6在通过+1,-2的操做后正确值为5,对了 可是若是这里不是c=c+1, c=c-2,而是c=x的方式,那么就 有可能会覆盖原值,因此在金额等的更改上,不该该取出 来再给数据库赋值,而是在原基础上进行加减。 |
链接1-事务1 | 链接2-事务2 |
mysql> start transaction; Query OK, 0 rows affected (0.02 sec) #由于b是惟一索引,因此这里把b=2的记录给加了个行 级锁,若是查询出的为空的话,则全表加锁,若是b不 是索引,则也是全表加锁 mysql> select * from t where b=2 for update; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 5 | +---+------+------+ 1 row in set (0.02 sec) |
|
mysql> start transaction; Query OK, 0 rows affected (0.02 sec) 此处 mysql> update t set c=2 where b=1; Query OK, 1 row affected (0.04 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
mysql> update t set c=3 where b=1; ... 此处阻塞住了 |
|
mysql> update t set c=6 where b=2; Query OK, 1 row affected (0.03 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction |
|
mysql> select * from t; +---+------+------+ | a | b | c | +---+------+------+ | 1 | 1 | 1 | | 2 | 2 | 5 | +---+------+------+ 2 rows in set (0.02 sec) |
|
mysql> commit; Query OK, 0 rows affected (0.03 sec) mysql> select * from t; +---+------+------+ | a | b | c | +---+------+------+ | 1 | 1 | 2 | | 2 | 2 | 6 | +---+------+------+ 2 rows in set (0.02 sec) |
|
mysql> select * from t; +---+------+------+ | a | b | c | +---+------+------+ | 1 | 1 | 2 | | 2 | 2 | 6 | +---+------+------+ 2 rows in set (0.04 sec) 可见update t set c=3 where b=1;这条语句没有执行, 事务2的所有执行成功了,由于在死锁发生时,事务1被 回滚了。具体参考下面的日志信息 |
使用show engine innodb status\G查看死锁信息,下面只摘取了死锁信息部分,其余的省略。.net
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2015-05-21 16:12:55 7fe02cfd2700 *** (1) TRANSACTION: ## 事务1 TRANSACTION 7651536, ACTIVE 218 sec starting index read ## 事务ID=7651536, 活跃了218秒 mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1248, 3 row lock(s) ## 有3个行锁 MySQL thread id 192071, OS thread handle 0x7fe02ce0b700, query id 13896576 114.112.84.198 root updating ## 该事务的线程ID=192071 update t set c=3 where b=1 ## 这是当前事务执行的SQL *** (1) WAITING FOR THIS LOCK TO BE GRANTED: ##等待要加的锁 RECORD LOCKS space id 3864 page no 4 n bits 72 index `ib` of table `test`.`t` trx id 7651536 lock_mode X locks rec but not gap waiting ## ## 等待在惟一索引ib上的page num=4上加一个X锁(lock_mode X locks rec but not gap)waiting意指等待的锁 Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 4; hex 80000001; asc ;;
*** (2) TRANSACTION: ## 事务2 TRANSACTION 7651538, ACTIVE 200 sec starting index read ## 事务ID=7651538, 活跃了200秒 mysql tables in use 1, locked 1 4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 1 ## 4个锁,3个行锁,1个undo log MySQL thread id 192072, OS thread handle 0x7fe02cfd2700, query id 13896591 114.112.84.198 root updating ## 该事务的线程ID=192072 update t set c=6 where b=2 ## 这是当前事务执行的SQL *** (2) HOLDS THE LOCK(S): ## 这个事务持有的锁信息 RECORD LOCKS space id 3864 page no 4 n bits 72 index `ib` of table `test`.`t` trx id 7651538 lock_mode X locks rec but not gap ## 在惟一索引ib上page num=4上已持有一个X锁 Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 4; hex 80000001; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED: ## 同时这个事务还等待的锁信息 RECORD LOCKS space id 3864 page no 4 n bits 72 index `ib` of table `test`.`t` trx id 7651538 lock_mode X locks rec but not gap waiting ## 一样等待在惟一索引ib上的page num=4上加一个X锁(lock_mode X locks rec but not gap) Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 4; hex 80000002; asc ;;
*** WE ROLL BACK TRANSACTION (1) ## 这里选择回滚了事务7651536 也就是事务7651536的sql没有执行 update t set c=3 where b=1 事务7651538的sql执行了 update t set c=6 where b=2 |
注意:若是autocommit为on的状态的话,必须手动begin开启事务,不然for update锁不住
off状态的话会自动锁(由于auto模式下sql执行完了就commit了)
链接1 - 事务1 | 链接2 - 事务2 |
mysql> start transaction; Query OK, 0 rows affected (0.02 sec) mysql> select * from t where b=2 for update; +---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 6 | +---+------+------+ 1 row in set (0.03 sec) |
|
mysql> start transaction; Query OK, 0 rows affected (0.02 sec) mysql> select * from t where b=2 for update; ... 此处阻塞住了 |
|
mysql> commit; Query OK, 0 rows affected (0.04 sec) |
|
+---+------+------+ | a | b | c | +---+------+------+ | 2 | 2 | 6 | +---+------+------+ 1 row in set (26.91 sec) |
有以下表:
MariaDB [t]> show create table t\G *************************** 1. row *************************** Table: t Create Table: CREATE TABLE `t` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `a` varchar(255) DEFAULT NULL, `b` varchar(255) DEFAULT NULL, `c` int(10) DEFAULT NULL, `d` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `index_b` (`b`) USING BTREE, KEY `index_a` (`a`) USING BTREE, KEY `index_c` (`c`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 1 row in set (0.00 sec) MariaDB [t]> SHOW VARIABLES LIKE '%AUTOCOMMIT%'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) MariaDB [t]> select @@global.tx_isolation; +-----------------------+ | @@global.tx_isolation | +-----------------------+ | REPEATABLE-READ | +-----------------------+ 1 row in set (0.00 sec) MariaDB [t]> select * from t; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | | 2 | 2 | 2 | 2 | 2 | | 3 | 3 | 3 | 3 | 3 | | 4 | 4 | 4 | 4 | 4 | | 5 | 5 | 5 | 5 | 5 | | 6 | 1 | 6 | 1 | 2 | | 7 | 1 | 7 | 1 | 2 | +----+------+------+------+------+ 7 rows in set (0.00 sec)
事务1 | 事务2 |
MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where id=1 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec)
|
|
# 未开启事务 MariaDB [t]> select * from t where id=1 lock in share mode; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where id=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> delete from t where id=1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> update t set d=1 where id=1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> delete from t where id=2; Query OK, 1 row affected (0.00 sec) MariaDB [t]> update t set d=1 where id=3; Query OK, 1 row affected (0.00 sec) # 开启事务 MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where id=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 手动开启事务后和上面结果同样,由于autocommit虽然自动commit, 但也得先拿锁,拿锁就得等锁释放
|
事务1 | 事务2 |
MariaDB [t]> select * from t where id=1 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) autocommit下自动commit,因此右边的可拿到锁, 因此for update必须在开启事务的前提下使用
|
|
MariaDB [t]> select * from t where id=1 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) 事务2没开事务,能查到 MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where id=1 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) 事务2开事务,仍是能查到
|
|
事务1 | 事务2 |
MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where id=1 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec)
|
|
MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where id=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 可见,id=1的被锁住了 MariaDB [t]> select * from t where id=3 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 3 | 3 | 3 | 3 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) 可见id=3的没被锁住,说明主键只锁一条 MariaDB [t]> insert into t(a,b,c,d) values(1,2,3,3); Query OK, 1 row affected (0.01 sec) MariaDB [t]> select * from t; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | | 3 | 3 | 3 | 3 | 1 | | 4 | 4 | 4 | 4 | 4 | | 5 | 5 | 5 | 5 | 5 | | 6 | 1 | 6 | 1 | 2 | | 7 | 1 | 7 | 1 | 2 | | 9 | 1 | 2 | 3 | 3 | +----+------+------+------+------+ 7 rows in set (0.00 sec) MariaDB [t]> delete from t where id=9; Query OK, 1 row affected (0.00 sec) MariaDB [t]> select * from t; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | | 3 | 3 | 3 | 3 | 1 | | 4 | 4 | 4 | 4 | 4 | | 5 | 5 | 5 | 5 | 5 | | 6 | 1 | 6 | 1 | 2 | | 7 | 1 | 7 | 1 | 2 | +----+------+------+------+------+ 6 rows in set (0.00 sec) MariaDB [t]> update t set d=3 where id=3; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [t]> select * from t; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | | 3 | 3 | 3 | 3 | 3 | | 4 | 4 | 4 | 4 | 4 | | 5 | 5 | 5 | 5 | 5 | | 6 | 1 | 6 | 1 | 2 | | 7 | 1 | 7 | 1 | 2 | +----+------+------+------+------+ 6 rows in set (0.00 sec)
|
|
如下为间隙锁讲解 MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where id=100 for update; Empty set (0.01 sec) MariaDB [t]> SHOW TABLE STATUS\G *************************** 1. row ***** Name: t Engine: InnoDB Version: 10 Row_format: Compact Rows: 7 Avg_row_length: 2340 Data_length: 16384 Max_data_length: 0 Index_length: 49152 Data_free: 0 Auto_increment: 12 ----当前自增id最大值 Create_time: 2017-08-24 14:34:25 Update_time: NULL Check_time: NULL Collation: utf8_general_ci Checksum: NULL Create_options: Comment: 1 row in set (0.00 sec) 当前获取到next-key间隙锁 id是自增索引,当前还未自增到100,因此有可能 新插入数据的id为100, 为了确保没有幻读,加了(12,max)的间隙锁
|
|
MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where id=1 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.01 sec) 由于锁住的是(12,max),因此前面的数据没有锁,当前可拿到锁 MariaDB [t]> insert into t(a,b,c,d) values(1,2,3,3); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 由于锁住的是(12,max),因此后面的被锁住,不能插入
|
|
假如以下: MariaDB [t]> select * from t where id=2 for update; Empty set (0.00 sec) 自增id不会再等于2,因此没有锁任何记录
|
|
MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where id=1 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) MariaDB [t]> update t set d=3 where id=3; Query OK, 0 rows affected (0.00 sec) Rows matched: 1 Changed: 0 Warnings: 0 MariaDB [t]> delete from t where id=3; Query OK, 1 row affected (0.00 sec) MariaDB [t]> insert into t(a,b,c,d) values(8,8,8,8); Query OK, 1 row affected (0.00 sec) MariaDB [t]> rollback; Query OK, 0 rows affected (0.00 sec) 可见可操做任何操做
|
事务1:
MariaDB [t]> begin; Query OK, 0 rows affected (0.01 sec) MariaDB [t]> select * from t where a='1' for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | | 6 | 1 | 1 | 1 | 2 | | 7 | 1 | 2 | 2 | 2 | +----+------+------+------+------+ 3 rows in set (0.00 sec) MariaDB [t]> explain select * from t where a='1' for update; +------+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ | 1 | SIMPLE | t | ref | index_a | index_a | 768 | const | 3 | Using index condition | +------+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+ 查看查询计划,用的索引,因此锁全表记录 必定要注意:varchar的索引若是查询时不用''单引号包起来不会走索引 MariaDB [t]> explain select * from t where a=1 for update; +------+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | t | ALL | index_a | NULL | NULL | NULL | 17 | Using where | +------+-------------+-------+------+---------------+------+---------+------+------+-------------+
事务2:
MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where a=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where a='1' for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where a='3' for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 3 | 3 | 3 | 3 | 3 | +----+------+------+------+------+ 1 row in set (0.00 sec) MariaDB [t]> select * from t where b='1' for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where b='3' for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 3 | 3 | 3 | 3 | 3 | +----+------+------+------+------+ 1 row in set (0.00 sec) MariaDB [t]> select * from t where id=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where id=3 for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 3 | 3 | 3 | 3 | 3 | +----+------+------+------+------+ 1 row in set (0.00 sec) 可见,只锁住了查询出来的3条记录,其他都未加锁 MariaDB [t]> insert into t(a,b,c,d) values('1','2',3,'3'); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> insert into t(a,b,c,d) values('2','2',3,'3'); Query OK, 1 row affected (0.00 sec) 可见插入时,a为'1'值的记录插入不进去,其他均可以 MariaDB [t]> update t set d='3' where id=1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> update t set d='3' where id=3; Query OK, 0 rows affected (0.00 sec) Rows matched: 1 Changed: 0 Warnings: 0 可见修改记录时也是,若是此条记录被锁住的话须要等待锁
事务1
MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where d='1' for update; +----+------+------+------+------+ | id | a | b | c | d | +----+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) MariaDB [t]> explain select * from t where d='1' for update; +------+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | t | ALL | NULL | NULL | NULL | NULL | 18 | Using where | +------+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec) 可见全表扫描
事务2
MariaDB [t]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [t]> select * from t where a='1' for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where a='1' for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where a='3' for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where b='1' for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where id=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [t]> select * from t where id=3 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 非主键索引时锁全表的记录,其余记录都获取不到锁(包括主键索引),因此一直等待到超时 MariaDB [t]> insert into t(a,b,c,d) values('1','2',3,'3'); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 非主键索引时锁全表的记录,可见插入不可插 MariaDB [t]> update t set d='3' where id=3; 修改记录也是不行的
在可重复读的隔离级别下,能够解决不可重复读,以及幻读(按定义是有幻读的,但mysql经过mvcc解决了(一致性读是经过 MVCC 为查询提供了一个基于时间的点的快照。这个查询只能看到在本身以前提交的数据,而在查询开始以后提交的数据是不能够看到的。在默认隔离级别REPEATABLE READ下,同一事务的全部一致性读只会读取第一次查询时建立的快照), 但加锁读仍是能够读到的),见下
MariaDB [demo]> desc user; +-------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------+-------+ | id | varchar(50) | NO | PRI | NULL | | | name | varchar(255) | YES | | NULL | | | create_time | datetime | YES | | NULL | | +-------------+--------------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
事务1 | 事务2 |
MariaDB [demo]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [demo]> select * from user; +----+------+---------------------+ | id | name | create_time | +----+------+---------------------+ | zq | zq1 | 2017-12-05 18:47:26 | +----+------+---------------------+ 1 row in set (0.00 sec)
|
|
MariaDB [demo]> begin; Query OK, 0 rows affected (0.00 sec) MariaDB [demo]> select * from user; +----+------+---------------------+ | id | name | create_time | +----+------+---------------------+ | zq | zq1 | 2017-12-05 18:47:26 | +----+------+---------------------+ 1 row in set (0.00 sec)
|
|
MariaDB [demo]> insert into user(id, name) values("zq2", "zq2"); Query OK, 1 row affected (0.00 sec) MariaDB [demo]> select * from user; +-----+------+---------------------+ | id | name | create_time | +-----+------+---------------------+ | zq | zq1 | 2017-12-05 18:47:26 | | zq2 | zq2 | NULL | +-----+------+---------------------+ 2 rows in set (0.00 sec)
|
|
MariaDB [demo]> select * from user; +----+------+---------------------+ | id | name | create_time | +----+------+---------------------+ | zq | zq1 | 2017-12-05 18:47:26 | +----+------+---------------------+ 1 row in set (0.00 sec) 此处查看不到事务1未commit的数据,解决脏读
|
|
MariaDB [demo]> commit; Query OK, 0 rows affected (0.02 sec)
|
|
MariaDB [demo]> select * from user; +----+------+---------------------+ | id | name | create_time | +----+------+---------------------+ | zq | zq1 | 2017-12-05 18:47:26 | +----+------+---------------------+ 1 row in set (0.00 sec) 此处事务1已commit数据,但仍是看不到,保证 了可重复复读以及幻读问题
|
|
MariaDB [demo]> select * from user for update; +-----+------+---------------------+ | id | name | create_time | +-----+------+---------------------+ | zq | zq1 | 2017-12-05 18:47:26 | | zq2 | zq2 | NULL | +-----+------+---------------------+ 2 rows in set (0.00 sec) 但若是加锁读的话是能看到的 其实,可重复读和提交读是矛盾的。在同一个事务里, 若是保证了可重复读,就会看不到其余事务的提交, 违背了提交读;若是保证了提交读, 就会致使先后两次读到的结果不一致,违背了可重复读。 InnoDB提供了这样的机制,在默认的可重复读的隔离级别里, 可使用加锁读去查询最新的数据。 这时就会对当前读数据加锁,像查全表是加所有锁
|
|
MariaDB [demo]> commit; Query OK, 0 rows affected (0.01 sec)
|
幻读问题是指一个事务的两次不一样时间的相同查询返回了不一样的的结果集。例如:一个 select 语句执行了两次,可是在第二次返回了第一次没有返回的行,那么这些行就是“phantom” row.
深一点这里讲解也不错
首先读分为:
快照读
select * from table where ?;
当前读:特殊的读操做,插入/更新/删除操做,属于当前读,须要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
全部以上的语句,都属于当前读,读取记录的最新版本。而且,读取以后,还须要保证其余并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其余的操做,都加的是X锁 (排它锁)。
对于快照读来讲,幻读的解决是依赖mvcc解决。而对于当前读则依赖于gap-lock解决(由于间隙锁会锁定当前查询间隙中暂时不存在的数据,其余事务想插入或删除或修改都会有当前这个间隙锁而被hang住)。
须要注意的是快照读是在select查询后而不是事务开启后
看下面一个异常现象(也不算异常了)
SESSION_A开始事务并建立快照,或一个普通的查询也会建立一个快照读 SESSION_A>START TRANSACTION WITH CONSISTENT SNAPSHOT; Query OK, 0 rows affected (0.00 sec) SESSION_B>begin; Query OK, 0 rows affected (0.00 sec) SESSION_A>select * from read_view; +-------------------------+ | text | +-------------------------+ | init | | after session A select | | before Session_A select | +-------------------------+ 3 rows in set (0.00 sec) SESSION_B>insert into read_view values('anomaly'),('anomaly'); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 SESSION_B>update read_view set text='INIT' where text='init'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 SESSION_B>commit; Query OK, 0 rows affected (0.00 sec) SESSION_A>select * from read_view; +-------------------------+ | text | +-------------------------+ | init | | after session A select | | before Session_A select | +-------------------------+ 3 rows in set (0.00 sec) # SESSION_A更新了它并无"看"到的行 SESSION_A>update read_view set text='anomaly!' where text='anomaly'; Query OK, 2 rows affected (0.00 sec) Rows matched: 2 Changed: 2 Warnings: 0 # 这里竟然看到了SESSION_B commit的数据了 SESSION_A>select * from read_view; +-------------------------+ | text | +-------------------------+ | init | | after session A select | | before Session_A select | | anomaly! | | anomaly! | +-------------------------+ 5 rows in set (0.00 sec) SESSION_A>commit; Query OK, 0 rows affected (0.00 sec) SESSION_A>select * from read_view; +-------------------------+ | text | +-------------------------+ | INIT | | after session A select | | before Session_A select | | anomaly! | | anomaly! | +-------------------------+ 5 rows in set (0.00 sec)
观察实验步骤能够发现,在倒数第二次查询中,出现了一个并不存在的状态,这里A的先后两次读,均为快照读,并且是在同一个事务中。可是B先插入直接提交,此时A再update,update属于当前读,因此能够做用于新插入的行,而且将修改行的当前版本号设为A的事务号,因此第二次的快照读,是能够读取到的,由于同事务号。这种状况符合MVCC的规则,若是要称为一种幻读也非不可,算为一个特殊状况来看待吧。
Innodb行锁分为:
类型 | 说明 |
---|---|
Record Lock: | 在索引上对单行记录加锁. |
Gap Lock: | 锁定一个范围的记录,但不包括记录自己.锁加在未使用的空闲空间上,多是两个索引记录之间,也多是第一个索引记录以前或最后一个索引以后的空间. |
Next-Key Lock: | 行锁与间隙锁组合起来用就叫作Next-Key Lock。锁定一个范围,而且锁定记录自己。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。 |
假若有下
(mysql@localhost) [fandb]> create table t5(id int,key(id)); Query OK, 0 rows affected (0.02 sec) (mysql@localhost) [fandb]> insert into t5 values(1),(4),(7),(10); Query OK, 4 rows affected (0.00 sec) Records: 4 Duplicates: 0 Warnings: 0
开始实验
SESSION_A>begin; Query OK, 0 rows affected (0.00 sec) SESSION_A>select * from t5; +------+ | id | +------+ | 1 | | 4 | | 7 | | 10 | +------+ 4 rows in set (0.00 sec) SESSION_A>select * from t5 where id=7 for update; +------+ | id | +------+ | 7 | +------+ 1 row in set (0.00 sec) SESSION_B>begin; Query OK, 0 rows affected (0.00 sec) SESSION_B>insert into t5 values(2); Query OK, 1 row affected (0.00 sec) SESSION_B>insert into t5 values(12); Query OK, 1 row affected (0.00 sec) SESSION_B>insert into t5 values(4); --被阻塞 ^CCtrl-C -- sending "KILL QUERY 93" to server ... Ctrl-C -- query aborted. ^[[AERROR 1317 (70100): Query execution was interrupted SESSION_B>insert into t5 values(5); --被阻塞 ^CCtrl-C -- sending "KILL QUERY 93" to server ... Ctrl-C -- query aborted. ^[[AERROR 1317 (70100): Query execution was interrupted SESSION_B>insert into t5 values(7); --被阻塞 ^CCtrl-C -- sending "KILL QUERY 93" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted SESSION_B>insert into t5 values(9); --被阻塞 ^CCtrl-C -- sending "KILL QUERY 93" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted SESSION_B>insert into t5 values(10); Query OK, 1 row affected (0.00 sec) SESSION_B>commit; Query OK, 0 rows affected (0.00 sec) SESSION_A>select * from t5; +------+ | id | +------+ | 1 | | 4 | | 7 | | 10 | +------+ 4 rows in set (0.00 sec) SESSION_A>commit; Query OK, 0 rows affected (0.00 sec) SESSION_A>select * from t5; +------+ | id | +------+ | 1 | | 2 | | 4 | | 7 | | 10 | | 10 | | 12 | +------+ 6 rows in set (0.00 sec)
当以当前读模式select * from t5 where id=7 for update;
获取 id=7的数据时,产生了 Next-Key Lock,锁住了4-10范围和 id=7单个record
从而阻塞了 SESSION_B在这个范围内插入数据,而在除此以外的范围内是能够插入数据的。
在倒数第二个查询中,由于 read view 的存在,避免了咱们看到 2和12两条数据,避免了幻读
同时由于 Next-Key Lock 的存在,阻塞了其余会话插入数据,所以当前模式读不会产生幻读(select for update 是以当前读模式获取数据)