笔者在使用mysql的时候,经常困惑于是否应该使用事务以及是否应该使用select...for update。于是做了一下测试。
事务是用来保证数据的一致性。一次提交的所有操作,是一组原子操作,要么全都成功,要么全都是失败。
1. 查看设置
autocommit默认开启,隔离级别为可重复读。
mysql> show variables like '%isolation%';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.00 sec)
mysql> show variables like '%autocommit%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
2. 为了测试,需要建表
mysql> create table transaction_test (
-> id int(11) NOT NULL AUTO_INCREMENT,
-> PRIMARY KEY (`id`)
-> ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.07 sec)
注意要使用支持事务的mysql引擎,比如InnoDB
3. MySQL默认操作模式就是autocommit自动提交模式。这就表示除非显式地开始一个事务,否则每个查询都被当做一个单独的事务自动执行。那么如果一次回车,有多条语句,会怎么样呢?
mysql> insert into transaction_test values(1); insert into transaction_test values(1);
Query OK, 1 row affected (0.01 sec)
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
mysql> select * from transaction_test;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
第一个语句成功了,第二个没有,说明多条语句会提交多次。
4. 事务的隔离性
# 在客户端A
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from transaction_test where id = 1;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
mysql> insert into transaction_test values(2);
Query OK, 1 row affected (0.00 sec)
mysql> insert into transaction_test values(1);
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
mysql> select * from transaction_test;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.00 sec)
# 打开另一个客户端B
mysql> select * from transaction_test;
+----+
| id |
+----+
| 1 |
+----+
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into transaction_test values(2);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
a) 设置autocommit=0,在事务没有显示的commit之前,那么修改就不会提交,如果断开了连接,就不会commit
b) 两个事务,锁冲突
c) 如果在一个事务中,语句抛出异常,事务不会commit,也不会回滚。
d) 隔离意味着并发
# 客户端A
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from transaction_test;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.00 sec)
# 客户端B
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from transaction_test;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from transaction_test;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.00 sec)
5. 可以看出 rollback事务其实并没有结束。还是需要commit一下,才能看到客户端A commit的数据。事务必须以commit结束。
个人理解: 有点类似于git中的master分支和其他分支的关系。mysql里commit的数据是master分支,开启事务可以类比成fork了一个分支出来,然后在自己的分支里修改,也只能看到自己的修改。区别在于,可能会对记录加锁,导致另一个事务insert卡住或失败。
如果要进一步研究的话,就要研究到事务的隔离级别和各种锁。
6. select...for update
for update仅适用于InnoDB,且必须在事务块中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。