mysql中锁的种类
有常见的表锁和行锁,也有metadata lock等,表锁是对一整张表加锁,虽然可分为读锁和写锁,但是锁整张表,会导致并发能力下降
行锁是锁住数据行,并发能力强,一般用行锁来处理并发事务.
read commited
数据的读取都是不加锁的,但是数据的写入,修改,删除是需要加锁的
对一个过滤有索引的字段,第一个线程进行修改时,比如update tablename set tname=’’ where tname=’a’,会对这行加锁,如果另一个线程也要修改这行字段,就会等待第一个线程释放锁.
如果这个字段没有索引,第一个线程进行修改时,就会对整个表的所有行加锁,因为不知道哪些行是tname=’a’,如果一个条件无法通过索引快速过滤,存储引擎就会将所有记录加锁后返回,有mysql server层进行过滤.
实际过程中,mysql做了改进,mysql server过滤条件后,发现不满足,会调用unlock_row方法,把不满足条件的记录释放锁.
repeatable read
innodb默认隔离级别.
先说不可重读
事务A
begin;
select * from tablename where tid=1;
事务B
begin;
update tablename set cid=2 where tid=1;
commit;
事务A同样查询后结果不一样,读到了事务B修改后的数据.
在说可重读,经过同样的操作后,没有读到事务B提交的数据.
不可重复读和幻读的区别
不可重复读的重点在于update 和delete,而幻读的重点在insert.
在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其他事物无法修改这些数据,就实现了可重复读.但这种方式无法锁住insert的数据,所以当事务A先读取了数据,或者修改了数据,事务B还是可以insert提交数据,事务A在读时,发现多了一条数据,这就是幻读,不能通过行锁来避免.需要Serializable隔离级别,读用读锁,写用写锁,读锁和写锁互斥,就可以有效的避免幻读,不可重复读,脏读,但是会极大降低数据库并发能力。
mvcc在innodb的实现
多版本并发控制
在innodb中,会在每行数据添加两个额外的隐藏的值来实现mvcc,这两个值一个记录这行数据何时被创建,另一个记录这行数据何时过期,在实际操作中,存储的不是时间,而是事务的版本号,没开启一个新事物,事物的版本号就会被递增.在可重读事务隔离级别如下
- select时.读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号
- insert时,保存当前事务版本号为行的创建版本号
- delete时,保存当前事务版本号为行的删除版本号
- update时,插入一条新记录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行.
通过mvcc,虽然每行记录都需要额外的存储空间,更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行.
mvcc虽然让数据变得可重复读,但是读到的不是最新的数据,在一些对数据敏感的业务中,就会出现问题
对于这种读取历史数据的方式,我们叫快照读,而读取数据库当前版本数据的方式,叫做当前读.
- 快照读就是select * from tablename
- 当前读:特殊的读操作,/插入/更新/删除操作,处理当前数据,需要加锁
- select * from table where ? lock in share mode
- select * from table where ? for update
- insert
- update
- delete
为了解决当前读的问题,mysql事务使用了next-key锁,是行锁和gap锁的合并.行锁可以防止不用事务版本的数据修改提交时造成的冲突,如何避免插入数据的问题呢?
用间隙锁gap,当更新某字段,不仅使用了行锁,同时也在此叶子节点数据两遍的节点加锁,这样事务就无法在这个字两遍的区间添加新数据.
行锁防止别的事务修改删除,gap锁防止别的事务新增,共同形成的next-key锁功能解决了幻读问题