锁的作用是协调多个进程或者线程并发访问资源的机制,其本质上是一个标志,程序根据这个标志来决定该怎么执行。
mysql中的锁按照粒度分三类:
1,全局锁
所有的表都加锁,比如数据备份的时候可以添加全局锁。sql语句:加锁:flush tables with read lock; 数据备份:mysqldump -uroot -p1234 database > database.sql; 解锁:unlock tables;
2,表级锁
只锁某张表,比如批量更新表数据时。sql: 加锁:lock tables tableName read/wirte;(分为共享读锁和独占写锁) 解锁:unlock tables;这是可以在开发过程中使用的表锁。
还有两种mysql自己维护的锁,一个是元数据锁,就是对表结构变更是不能增删改数据,同样的增删改数据时就不能修改表结构这个通过元数据锁实现。
二是意向锁,避免表锁和行锁冲突时的意外开销。当表添加了行锁时,mysql会自动添加意向锁,不允许再给该表添加表锁直到行锁释放。也分读写锁或者说共享排他或者独占锁。
3,行级锁
只锁某一行,是InnoDB支持的锁形式,InnoDB的特点就是(事务,行锁,外键);
也分读写锁,其中在执行增删改时都会自动添加写锁。也可以手动添加:读锁 select * from t where t.id = '1' lock in share mode;写锁: select * from t where t.id = '' for update;
根据粒度可以分为行锁(record lock 锁定单行记录)间隙锁(gap lock 锁定两个索引之间的数据)临键锁(next-key lock)。
然后就是mysql事务及其原理了。
首先mysql事务存在的意义就是保证数据的原子性,一致性和持久性,而InnoDB取代MyISAM最主要的原因肯定也是因为它支持事务,如果数据库不支持事务,那后果不敢想象。
还有一个问题就是mysql的事务是基于连接的,不同的连接肯定在不同的事务里面。
然后事务怎么支持这些特性呢?
1,mysql通过手动控制提交或者回滚,来保证数据的原子性一致性和持久性,多条更新语句全部执行完毕之后再提交到硬盘保存,如果中间有失败的就直接回滚。具体要了解怎么做的就要了解mysql更新语句的执行过程:
a,加载需要更新的数据到内存缓存区,
b,记录undolog日志,这个是回滚必须要做的系统开支,否则没法回滚,加载到内存中的数据行会记录指向这条log的指针,记录的位置就是mysql给每个数据行都配置的隐式字段db_roll_ptr,这个之后还会提。
c,更新内存中的数据,此时内存中的数据与硬盘的数据不一致,该内存数据称之为脏数据,凡是已提交的脏数据都会有额外的线程自动刷新到硬盘上。
d,记录redolog日志,称之为重做日志,这个日志效率较高,用于当脏数据丢失时的数据重做。
e,redolog写完之后,记录binlog二进制日志,redolog会在数据刷新到硬盘之后自动删除,二进制日志不会,可以用来数据的备份恢复等。
f,binlog日志记录完成之后就可以标记为可提交或者自动提交,执行提交操作之后数据可以自动刷新进持久化磁盘。
2,既然引入了undolog,就不得不提另外一个事情就是,同一份数据现在有多个版本,而且mysql把同一份数据的多个undolog维护成一个链表,称之为版本链。
那一个事务或者说连接该读取那一份呢?这个事情就跟另外一个特性隔离性有关。
首先要理解这个隔离性是指多个事务一起执行时,可能出现脏读(读到了其他事务为提交的数据),不可重复读(两次读取同一条数据结果不一致)和幻读(两次读取数据条数不一致)的问题
为了解决或者部分解决这个问题,出现了事务的隔离级别的概念,有四个,读未提交,读已提交,可重复度和串行化。其中读未提交和串行化比较简单,一个是直接读最新行数据一个化并行为串行。
但是读未提交的数据在业务逻辑中用处不大,而串行化确实安全但是并发性又不好,不允许两个事务同时写,但是一个事务写另外的事务读总是可以的吧,为了保证读写没有冲突,mysql设计了 MVCC,称之为多版本并发控制,这个机制保证了mysql的非阻塞读特性。
非阻塞读又称之为快照读,与快照读相对应的是当前读,当前读是串行化读取,只有在加锁的情况下才会执行当前读,否则都是快照读,快照读读取的不一定是最新数据,而是该数据行所有undo log版本链中的某一条数据,至于是哪一份就是MVCC控制的,MVCC相对较复杂。
尝试理解一下:
1,首先要理解undolog版本链的存在,每次更新操作都会往这个链表头插入一条未更新前的已提交的数据
2,mysql数据行隐式字段,之前我们在介绍undolog日志时,说过mysql每行数据都会维护一个隐式的回滚数据指针字段,以便数据的回滚,除了这个之外还有两个隐式字段,一个是最近事务ID db_trx_id,另一个是隐式主键(如果没有指定主键的话)。
3,读视图,然后根据MVCC规则,在读已提交的隔离级别下每次读取都会生成一个读视图,记录当前状态下所有活跃的ID,将要分配的事务ID,还有当前事务ID。可重复读是事务开启后第一次读取会生成读视图,之后每次都使用这个视图。
MVCC具体规则就是,沿着undolog版本链一个个比较,如果有符合一下规则的就可以读取:
1,如果被访问的版本数据中最近的一次事务ID与当前事务ID相同就会直接读取当前版本数据。
2,如果被访问的版本数据中最近的一次事务ID比读视图记录的最小活跃ID要小,说明该版本记录已提交可以读。
3,如果被访问的版本数据中最近的一次事务ID比读视图记录的将要分配的ID要大或者相等,说明这个是个新事务,该数据不可读,沿着undolog链继续往后找。
4,如果被访问的版本数据中最近的一次事务ID是所有活跃ID中的一个,不能访问该版本数据。