mysql事务

简介:

在默认的情况下,MySQL在自动提交(autocommit=1/ON)模式运行,这种模式会在每条语句执行完毕后把它作出的修改立刻提交给数据库并使之永久化。事实上,这相当于把每一条语句都隐含地当做一个事务来执行。如果你想明确地执行事务,需要禁用自动提交模式并告诉MySQL你想让它在何时提交或回滚有关的修改。

我们想要明确的执行事务的话,用START TRANSACTION(或BEGIN)开启事务其实它会自动挂起自动提交事务模式(即会忽略autocommit=1),然后执行本次事务的各语句,最后用COMMIT语句结束事务并把它们做出的修改永久性记入数据库。万一事务过程中发生错误,用一条ROLLBACK语句撤销事务并把数据库恢复到事务开始之前的状态。

START TRANSACTION语句在COMMIT/ROLLBACK之后会做什么?

答:在事务被提交或回滚之后,该模式将恢复到开始本次事务的START TRANSACTION语句被执行之前的状态,这里有两种情况:1.如果自动提交模式原来是激活的,结束事务将让你回到自动提交模式

           2.如果它原来是非自动提交模式的,结束当前事务将开始下一个事务

下面实例说明

假如有表 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)


以下全为自动模式下做的实验:

1。查看自动提交模式数据提交情况。连接1,连接2都未开启事务

连接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的更改


2. 查看自动提交模式下start transaction的作用。连接1开启事务,连接2未开启

连接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


3.  连接1,连接2同时开启事务时

连接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)
可见已变


4. 假如有这样的场景,连接1里给c+1,连接2里的给c-2

连接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的方式,那么就
有可能会覆盖原值,所以在金额等的更改上,不应该取出
来再给数据库赋值,而是在原基础上进行加减。


5. 下面测试下死锁情况

连接1-事务1连接2-事务2
mysql> start transaction;                   
Query OK, 0 rows affected (0.02 sec)
#因为b是唯一索引,所以这里把b=2的记录给加了个行
级锁
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被回滚了。具体参考下面的
日志信息


5.1  死锁日志分析

使用show engine innodb status\G查看死锁信息,下面只摘取了死锁信息部分,其他的省略。


------------------------

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


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。