MySql - 事务 | 锁
主要内容:
- 事务
- 锁
- 事务实例
1. 事务
1.1 事务简介
事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消
1.2 事务的特性
事务具有四个特征:
- 原子性( Atomicity ) :表示组成一个事务的多个数据库操作是一个不可分隔的原子单元,只有所有的操作执行成功,整个事务才提交事务中任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态。
- 一致性( Consistency ):事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。
- 隔离性( Isolation ):在并发数据操作时,不同的事务拥有各自数据空间,它们的操作不会对对方产生干扰。数据库规定了多种事务隔 离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。
- 持续性( Durability) :一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证能够通过某种机制恢复数据。
2. 锁
2.1 锁简介
数据库中的锁是指一种软件机制,用来控制防止某个用户(进程会话)在已经占用了某种数据资源时,其他用户做出影响本用户数据操作或导致数据非完整性和非一致性问题发生的手段
2.2 锁的划分
(1)根据锁的级别划分可以划分为共享锁,排它锁
- 共享锁:针对同一条数据,多个读操作可以同时进行而不会互相影响.共享锁只针对update时候加锁,在未对update操作提交之前,其他事务只能获取最新的记录但不能够update操作
- 排它锁:当前的写操作没有完成前,阻断其他的写锁和读锁
(2)根据锁的粒度划分
- 行锁: 开销大,加锁慢,会出现死锁,锁定力度最小,发生锁冲突的概率最低,并发度高。
- 表所: 开销小,加锁快,不会出现死锁,锁定力度大,发生冲突所的概率高,并发度低
- 页面锁: 开销和加锁时间介于表锁和行锁之间,会出现死锁,锁定力度介于表和行行级锁之间,并发度一般
2.3 表锁
(1) 表锁的概述
- MyISAM存储引擎:为了保证数据的一致性,更改数据时,防止其他人更改数据,可以人工添加表级锁。可以使用命令对数据库的表枷锁,使用命令对数据库的表解锁。
- 给表加锁的命令Lock Tables,给表解锁的命令Unlock Tables
- MyISAM引擎在用户读数据自动加READ锁,更改数据自动加WRITE锁。使用lock Tables和Unlock Tables显式加锁和解锁
(2) 添加表 读锁
- 在一个数据库中创建两张表:
mysql> create table t1( -> id int, -> name char(20), -> age int -> )ENGINE=MyISAM; Query OK, 0 rows affected (0.16 sec) mysql> create table t2( -> id int, -> name char(20), -> age int -> )ENGINE=MyISAM; Query OK, 0 rows affected (0.35 sec)
- 在t1中插入两条记录
insert into t1 values(1, 'alex', 19); insert into t1 values(3, 'alex1',18);
- 对t1 加锁
mysql> lock tables t1 read; Query OK, 0 rows affected (0.00 sec)
- 加锁后只可以查询已经加锁的表t1,查询没有加锁的表时候会报错,而且当进行更新操作时候也会报错
mysql> select * from t1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | alex | 19 | | 2 | alex2 | 18 | +------+-------+------+ 2 rows in set (0.00 sec) mysql> select * from t2; ERROR 1100 (HY000): Table 't2' was not locked with LOCK TABLES mysql> update t1 set age=100 where id =1; ERROR 1099 (HY000): Table 't1' was locked with a READ lock and can't be updated
- 另起一个会话,对已经加锁的表进行查询,成功,但是当要进行更新操作会holding 即出现锁等待
mysql> use db20; Database changed mysql> select * from t1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | alex | 19 | | 2 | alex2 | 18 | +------+-------+------+ 2 rows in set (0.00 sec) mysql> update t1 set age=100 where id =2;
- 此时将t1解锁,另一个会话中的更新操作会成功
# 会话一中 mysql> unlock tables; Query OK, 0 rows affected (0.00 sec)
mysql> update t1 set age=100 where id =2; Query OK, 1 row affected (13 min 22.04 sec) Rows matched: 1 Changed: 1 Warnings: 0
另外一个会话的更新操作成功
- 在会话1中再次锁定t1表,后面带local参数
mysql> lock tables t1 read local; Query OK, 0 rows affected (0.00 sec)
在会话2中插入一条数据
mysql> insert into t1 values(3, 'alex2',17); Query OK, 1 row affected (0.00 sec)
但是 在会话1中查询发现并没有插入记录
mysql> select * from t1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | alex | 19 | | 2 | alex2 | 100 | +------+-------+------+ 2 rows in set (0.00 sec)
(3) 设置表锁的并发性
- READ锁是共享锁,不影响其他会话的读取,但不能更新已经加READ锁的数据。
- MyISAM表的读写是串行的,但是总体而言的,在一定条件下,MyISAM表也支持查询和插入操作的并发进行
注: MyISAM存储引擎有一个系统变量concurrent_insert,用以控制其并发插入的行为,其值分别可以为0、1或2。
- 0:不允许并发操作
- 1:如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录,是MySQL的默认设置。
- 2:无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。
在MySQL配置文件添加,concurrent_insert=2,重启mySQL服务设置生效。
2.4 行锁
(1) 行锁简介
- InnoDB存储引擎实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control)。
- MVCC的优点是读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。
在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)
- 快照度: 读取的是记录的可见版本 (有可能是历史版本),不用加锁。
- 当前读: 读取的是记录的最新版本,并且当前读返回的记录都会加上锁,保证其他事务不会再并发修改。事务加锁,是针对所操作的行,对其他行不进行加锁处理。
基于不同对的sql语句对读操作分类
- 快照读: 简单的select 操作,属于快照读,不加锁
select * from table where ?;
- 当前读: 特殊的读操作,insert,update,delete,属于当前读,需要加锁
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 ?;
以上sql操作属于当前读,读取记录的最新版本,并且读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。
(2) 验证快照读
- 打开会话1 创建表和数据
mysql> create table t3( -> id int, -> name char(20), -> age int -> )ENGINE=innoDB; Query OK, 0 rows affected (0.60 sec) mysql> insert into t1 values(1, 'jerry', 19); Query OK, 1 row affected (0.00 sec) mysql> insert into t1 values(2, 'tom',18); Query OK, 1 row affected (0.00 sec)
在会话1中开启事务,并查询 id =1 的数据
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t3 where id=1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | jerry | 19 | +------+-------+------+ 1 row in set (0.00 sec)
- 在会话2中更新id =1 的age为100,并查询数据内容
mysql> update t3 set age=100 where id =1; Query OK, 1 row affected (0.36 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from t3 where id =1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | jerry | 100 | +------+-------+------+ 1 row in set (0.00 sec)
- 在会话1中查看id为1的数据信息
mysql> select * from t3 where id=1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | jerry | 19 | +------+-------+------+ 1 row in set (0.00 sec)
但是当会话1中提交事务后 再次查询时候此时数据已进行了更改
mysql> commit -> ; Query OK, 0 rows affected (0.00 sec) mysql> select * from t3 where id=1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | jerry | 100 | +------+-------+------+ 1 row in set (0.00 sec)
(3) 验证当前读
- 会话1开启事务,并且给select语句添加共享锁。
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t3 where id =1 lock in share mode; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | jerry | 100 | +------+-------+------+ 1 row in set (0.00 sec)
- 在会话2中更新id = 1 的数据,此时会进入锁等待
mysql> update t3 set age=66 where id =1;
- 当会话1提交事务后,会话2的更新操作成功
mysql> commit -> ; Query OK, 0 rows affected (0.00 sec)
mysql> update t3 set age=66 where id =1; Query OK, 1 row affected (11.54 sec) Rows matched: 1 Changed: 1 Warnings: 0
(4) 验证事务给记录加锁
- 会话1 开启事务, 在会话1中更新 id = 1 的数据
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> update t3 set age=300 where id=1; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0
- 在会话2 开启事务 ,更新id为2 的数据 ,此时会进入锁等待
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> update t3 set age=200 where id=2;
由于 t3 表没有指定主键,事务不支持行级锁。会话1的事务给整张表加了锁
- 在会话1提交事务,此时会话2的修改成功 ,在会话2提交事务,解除对表的锁定
- 在会话1 给表的 id增加主键
mysql> alter table t3 add primary key(id); Query OK, 0 rows affected (0.59 sec) Records: 0 Duplicates: 0 Warnings: 0
在会话1 开启事务,并且更新id 为1 的数据
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> update t3 set age=300 where id=1; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0
- 在会话2上开启事务,并修改id为2的的数据,更新成功 ,说明会话1只锁定了ID为1的行
mysql> update t3 set age=400 where id=2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings:
但是在会话2 中更新id = 1 的值,出现了等待,因为会话1给ID为1的行添加了独占锁
mysql> update t3 set age=700 where id=1;
- 当在会话1和会话2 中都提交事务后,会话1和和会话2 的修改都生效
(5)死锁的产生
A事务添加共享锁后,B事务也可以添加共享锁。A事务update锁定记录,处于等待中,于此同时B事务也update更新锁定的记录,就产生死锁。
- 会话1 开启事务,查询id=1 的数据,并添加共享锁
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t3 where id =1 lock in share mode; +----+-------+------+ | id | name | age | +----+-------+------+ | 1 | jerry | 700 | +----+-------+------+ 1 row in set (0.00 sec)
- 同样,在会话2中 开启事务,查询id=1 的数据,并添加共享锁
mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from t3 where id =1 lock in share mode; +----+-------+------+ | id | name | age | +----+-------+------+ | 1 | jerry | 700 | +----+-------+------+ 1 row in set (0.00 sec)
- 在会话1 中更新 id=1 的数据,等待会话2释放共享锁
mysql> update t3 set age =199 where id =1;
- 在会话2更新ID为1的age为,会话2发现死锁,回滚事务。
mysql> update t3 set age =199 where id =1;
- 会话1 提交事务
commit
3. 事务实例
3.1 由错误决定事务提交还是回滚
3.2 由自定义事件决定事务提交还是回滚
------ 文章出处 https://blog.51cto.com/9291927/2096680 ----------