mysql事务隔离级别问题及锁知识基础

mysql 常用存储引擎(myisam,innodb)

	5.5版本之后默认存储引擎为 innodb

myisam存储引擎
不支持事务和外键,表储存三个文件,(.frm:储存表定义 MYD:存储数据 MYI:存储索引),数据和索引放置在不通的目录,平均分布IO获得更快的速度。
三种存储格式: 静态表(默认)固定长度 动态表 压缩表

innodb存储引擎
数据本身都是以聚簇索引的形式保存,本身没有索引会自动创建长度为6字节的long类型的隐藏字段作为索引
存储方式 1.使用共享表的空间存储,这种表结构保存在(.frm)文件中数据索引保存在innodb_data_home_dir和innodb_data_file_path定义的表空间中,可以多个文件
2.使用多表空间存储,这种方式创建的表结构仍然存在(.frm),但每个表地数据和索引单独保存在(.idb)中,如果分区表,则分区表对应单独的(.idb)文件,文件名‘表名加分区名’

要设置多表空间存储,需要设置参数innodb_file_per_table为on(5.7默认也是多表空间存储方式)

存储引擎的选择
innodb 除了插入和查询以外,还包含更多的更新删除,有效降低由于删除和更新导致的锁定,确保事务的完整提交和回滚的计费系统。

事务

	innodb不存在锁升级的问题,因为其不是根据每条记录来产生行锁的,是根据每个事务访问的每页对锁进行管理。

事务导致的问题

脏读
sql b:money + 100 a:money - 100
当第一条sql执行玩第二条还没有执行,如果b查询自己的账户,就会发现自己已经收到100,如果a等b完成后再回滚,b就会损失100元;

幻读
统计金额时查询两次,第一次查查询完后有一笔新的金额入账导致第二次查询和第一次查询不一致

不可重复读
一个事务中多次读同一数据,再这事务还没技术时另外的事务也访问到该数据。那么由于第二次事务将数据修改导致两次数据不一样。
更新丢失(多线程程序)
事务a更改数据,事务b也进行修改,导致a被b覆盖

事务隔离级别

未授权读取
也称读未提交:引发脏读,不可重复读和虚读,但避免更新丢失。 (如果一个事务已经开始写数据,则另一个事务不允许同时进行写操作,但允许其他事务读,该隔离级别可以通过‘排他写锁’实现)。
a窗口
set transaction isolating level read uncommitted;
(设置a用户的数据库隔离级别为Read uncommitted 读未提交)
start transaction ;
select * from account ;
select * from account ;
b窗口
transaction start ;
update account set money = money +100 where name = a;

授权读取
读已提交会引发不可重复读和虚度,避免脏读(瞬间共享锁和排他写锁实现,读取数据的事务允许其他事务读取该行数据,但未提交的写数据禁止事务访问改行)

a窗口
set transaction isolating level read committed;
(设置a用户的数据库隔离级别为Read committed 读提交数据)
start transaction ;
select * from account ;
select * from account ;
b窗口
transaction start ;
update account set money = money +100 where name = a;
commit;

可重复读(默认)
禁止不可重复读和脏读但有时可能出现幻读数据和虚读(这可通过共享锁和排他写锁实现读取数据的事务将禁止写事务但允许读事务)写事务则禁止任何其他事务;
a
set transaction isolation leve repeatable read
(设置a用户数据库隔离级别为repeatable read 可重复读)
start transation ;
select * from account ;
select * from account ;
b
start transation;
insert into account (name,money) value (gg,100)
commit ;

序列化serializable
提供严格的事务隔离,他要求事务序列化执行,仅通过行级锁无法实现序列化,必须通过其他计知保证新插入数据不会被纲脂性查询操作事务访问到

set transaction isolation serializable;
start transaction ;
select * from account ;
select * from account ;
b
start transation;
insert into account (name,money) value (gg,100);(不能插入只能a事务提交才能插入否则一只等待)
commit ;

总结事务隔离问题

脏读 :思路读已提交
设置事务隔离级别为 read committed 或者 repeatable read都是可以的

a客户端的级别时数据库默认repeatable read
b客户端的级别更改为效率更高read committed

幻读:思路读禁止写,序列化

修改事务隔离级别为repeatable read 或者 serializable

1.repeatable read 从理论角度会出现幻读,这也就限制拉repeatable read 这个事务隔离级别的使用,一个事务隔离别推出使用发现其自身缺陷,开发这自然会想到完善的方法,所以mysql内部通过多版本控制机制【实际上就是对读取数据加锁】解决这个问题,最后,用户才可以放心大胆使用repeatable read这个事务隔离级别 。

2.serializable 和 repeatable read 都可以防止幻读 但serializable 事务隔离级别效率低下,比较消耗数据库性能一般不是用

不可重复读
设置事务隔离级别 repeatable read ,serializable 不推荐使用

更新丢失

serializable虽然可以但数据库不会用这个隔离级别效率太低

1.使用排他锁(悲观锁)。
经过上面基于数据库锁介绍可知,丢失更新可以使用写锁(排他锁)进行控制,因为排他锁添加到某个表的时候,事务未经提交,其他事务根本没法获取修改权限,因此排他锁可以控制丢失更新。
需要说明的是有时候当知道某一行会发生修改的时候,可以把锁定范围缩小,例如使用select *from t_account where id =1 for update; 这样能够比较好的把控上锁的颗粒度,这种基于行级上锁的方法叫行级锁。
2.使用乐观锁
乐观锁原理:认为事务不一定会产生丢失更新,让事务进行并发修改,不对事务进行锁定,发现并行修改某行数据是,乐观锁抛出异常,让用户解决。
可以通过给数据表添加自增version或者时间戳timestamp.进行数据修改时数据库会检测versionzi字段和时间戳是否于原来的一致,抛异常。
检验事务b于version值,事务b提交前的version字段为1,当version为2,禁止事务b提交抛出异常让用户处理

补充

1.sql规范所规定的标准,不同数据库具体的实现可能会有些差异
2.mysql中默认事务隔离级别时可重复读时并不会锁住读取到的行
3.事务隔离级别为读提交时,写数据只会锁住相应的行
4.事务隔离级别为可重复读时,如果又索引(包括主键索引)的时候,以索引列为条件更新数据,会存在间隙锁,行锁,下一键锁的问题,从而锁住一些行,如果没有索引,更新数据时会锁住整张表。
5.事务隔离级别为串行锁时,读写会锁住整张表
6.隔离级别越高,越能保证数据的完整性和一致性,但时对并行性能的影响也越大,鱼和熊掌不可兼得,对于多数应用程序,可以优先考虑把数据系统隔离级别设为read committed(读已提交),他能避免脏读取,而且既有较好的并发性能,尽管他会导致不可重复度,幻读这些并发问题,在可能出现这类问题的个别场合,可以又应用程序采用悲观锁或乐观锁来控制。

下一篇锁机制及应用

你可能感兴趣的:(MySQL,1024程序员节,mysql,sql)