数据库的锁

按操作的类型分为读锁和写锁,读锁之间不会互斥

按锁的粒度分为表锁和行锁

表锁

MyISAM。开销小(一张表一个锁),加锁快(不需要先找到数据,直接对表加锁),无死锁,锁的粒度大,锁冲突概率高,并发低

对读操作加读锁,对增删改加写锁。

行锁

InnoDB。开销大,加锁慢,会出现死锁,粒度小,锁冲突概率低,并发高

Innodb行锁给主键/聚集索引上的索引项进行加锁

只有通过索引条件进行数据检索,才会使用行锁,否则,使用表锁(锁住索引的所有记录)

即锁住的是聚集索引的索引项(相当于锁定记录),如果通过其他索引加锁,其他索引要加锁,主键也要加锁,才能防止其他线程操作具体记录

锁的算法

临键锁(行锁默认算法) next-key lock 锁住命中索引的记录,以及下一个区间。查询条件为范围查找 between and、>、

mysql的事务锁代码实现 mysql事务加锁_隔离级别

原因:B+树的索引判断规则是左开右闭,它把这两个区间锁住(需要实际的索引节点区间锁住)。才能阻止(5,9)的数据插入,因为索引上没有(5,9)区间,只能把包含它们的区间都锁上

为什么使用临键锁作为默认行锁算法?

为了解决幻读的问题

间隙锁 gap lock 范围没有命中实际数据,退化成间隙锁。>4 and <6 锁住的是(4,7)

记录锁 record Lock 唯一索引命中单个记录(主键)

三者区别

key-next lock 命中了数据

gap lock 没有命中数据

record lock 命中了一条数据,且是唯一索引

事务

事务特性ACID

A原子性 atomicity

C一致性 consistent

I隔离性 isolation

D持久性 durable

并发事务处理带来的问题

更新丢失

当多个事务选择同一行,然后都基于各自最初选定的值去更新同一行数据的时候,由于事务之间没有互相感知,就会发生更新丢失的问题–后面的更新覆盖了前面的更新操作

脏读

一个事务读到了另一个事务未提交的修改数据

不可重复读

在一个事务内两次读取同一行数据,两者却不一样。即事务A读取到了事务B已经提交的修改数据,不符合隔离性

幻读

事务A读取到了事务B已提交的增加数据,不符合隔离性

事务隔离级别

隔离级别脏读(dirty read)不可重复读(nonrepeatable read)幻读(phantom read)

读未提交(read uncommited)可能可能可能

读已提交(read committed)不可能可能可能

可重复读(repeatable read)不可能不可能可能

可串行化(serializable)不可能不可能不可能

MVCC 多版本并发版本控制

每条记录都有字段:数据行版本号、删除的版本号

每个操作的数据开始时,都会从数据库申请一个事务ID(版本号)

新增数据:数据行版本号=事务ID、删除的版本号=null

删除数据:删除的版本号=事务ID

更新数据:删除的版本号=事务ID,将当前记录拷贝一份,新的记录走新增的操作

查询数据:数据行版本号<=事务ID,删除的版本号>事务ID或者为NULL,的记录

不能解决脏读,如果一个事务修改了数据,另一个事务开始了,再读数据,就可以读到第一个事务未提交的修改(脏读)

Undo Log(innodb)

undo log 保证事务的原子性

事务处理过程中如果出现了错误或者用户执行了rollback,mysql利用Undo log 中的备份将数据恢复到事务开始之前的状态

衍生出快照

去修改数据的时候,会把要修改的数据备份到Undo Buffer中,rollback的时候可以直接拿undo buffer中的数据拿去恢复

快照读:select发现数据上锁,就去读快照 快照读+mvcc解决幻读的问题

当前读:通过锁的机制操作数据,修改数据,显示加锁,for update 、in share mode

事务内的操作会建立快照

查询的是快照,操作数据(包括第一次查询)的时候建立快照

修改数据会更新快照

更新不应该把值拿出来再更新,而应该使用语句

update t1 set b= b-100 where id = 109;
Redo Log

redo log指事务汇总操作的任何数据,将最新的数据备份到redo log

在事务的执行过程中,开始写入redo log,并不是提交事务才写入redo log

redo log 保证事务的持久性

如何保证事务的持久性:防止在发生故障的时候,有脏页尚未写入磁盘,在重启mysql服务的时候,根据redo log重做,从而达到事务的未入磁盘数据进行持久化这一特性

保证了cache内存中的已经commit的操作,一定会持久化到磁盘中

事务的过程中,修改操作会开始放入redo buffer,commit的时候,redo buffer 同步到redo log

redo buffer和redo log之间是是顺序io,即从尾部添加内容,且它们之间有一些策略保证写入redo buffer的数据一定会写入redo log中,考虑了断电的情况

数据库重启的时候,会把 redo log 的数据恢复到表中的硬盘区

mysql的事务锁代码实现 mysql事务加锁_隔离级别_02

指定的redo log记录在ib_logfile1和ib_logfile2中,可以通过innodb_log_group_home_dir指定存储目录

一旦事务成功提交切数据持久化到磁盘,此时redo log中对应的事务数据就没有用了,所以redo log的写入是日志文件循环写入的,

对应的配置

innodb_log_files_in_group=2 指定redo log日志文件组中的最大数量
innodb_log_file_size=48M每一个日志文件最大大小
innodb_log_buffer_size=16M指定redo log在cache/buffer中的buffer大小

持久化策略 innodb_flush_log_at_trx_commit

0每秒提交,可能丢失1秒内的事务数据 redo buffer->redo log os cache->flush cache to disk

1默认值每次事务提交执行redo buffer->redo log os cache->flush cache to disk,最安全,不会丢数据,但是性能最差

2每次事务提交执行redo buffer->redo log os cache再每秒执行->flush cache to disk

MySQL的RC和RR的区别

1、RC没有gap lock

2、RC 通过 where 条件过滤之后,不符合条件的记录上的行锁,会释放掉(虽然这里破坏了“两阶段加锁原则”);

复制

1、RC 隔离级别不支持 statement 格式的bin log,因为该格式的复制,会导致主从数据的不一致;只能使用 mixed 或者 row 格式的bin log;

2、MySQL5.6 的早期版本,RC隔离级别是可以设置成使用statement格式的bin log,后期版本则会直接报错;

一致性读方面(不可重复读)

RC隔离级别时,事务中的每一条select语句会读取到其他事务已经提交了的记录,也就是每一条select都有自己的一致性读ReadView; 而RR隔离级别时,事务中的一致性读的ReadView是以第一条select语句的运行时,作为本事务的一致性读snapshot的建立时间点的。只能读取该时间点之前已经提交的数据。

RC 支持半一致性读,RR不支持

RC隔离级别下的update语句,使用的是半一致性读(semi consistent);而RR隔离级别的update语句使用的是当前读;当前读会发生锁的阻塞。

1> 半一致性读:A type of read operation used for UPDATEstatements, that is a combination of read committed and consistent read. When an UPDATE statement examines a row that is already locked, InnoDB returns the latest committed version to MySQL so that MySQL can determine whether the row matches the WHERE condition of the UPDATE. If the row matches (must be updated), MySQL reads the row again, and this time InnoDB either locks it or waits for a lock on it. This type of read operation can only happen when the transaction has the read committed isolation level, or when the innodb_locks_unsafe_for_binlog option is enabled.简单来说,semi-consistent read是read committed与consistent read两者的结合。一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足 update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)。semi-consistent read只会发生在read committed隔离级别下,或者是参数innodb_locks_unsafe_for_binlog被设置为true(该参数即将被废弃)。对比RR隔离级别,update语句会使用当前读,如果一行被锁定了,那么此时会被阻塞,发生锁等待。而不会读取最新的提交版本,然后来判断是否符合where条件。半一致性读的优点:减少了update语句时行锁的冲突;对于不满足update更新条件的记录,可以提前放锁,减少并发冲突的概率。