在处理并发读或者写时,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)。读锁是共享的,或者说是相互不阻塞的,写锁是排他的,也就是说一个写锁会阻塞其他的写锁和读锁。
锁粒度:在给定的资源上,锁定的数据量越少,则系统的并发程度越高。
事务就是一组原子性的SQL查询,或者说一个独立的工作单元。事务内的语句要么全部执行成功,要么全部执行失败。
事务有ACID特性,A表示原子性,C表示一致性,I表示隔离性,D表示持久性。
原子性:一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。
一致性:数据库总是从一个一致性的状态转换到另外一个一致性的状态。
隔离性:通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。
持久性:一旦事务提交,则其所做的修改就会永久保存到数据库中。
隔离性其实比想象的要复杂,在SQL标准中定义了四种隔离级别,read uncommitted(未提交读)、read committed(提交读)、repeatable read(可重复读)、serializable(可串行化)
在read uncommitted(未提交读)级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读。
read committed(提交读),大多数数据库系统的默认隔离级别都是read committed(但MySQL不是)。一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。
可重复读(repeatable read),可重复读解决了脏读的问题。该级别保证了在同一个事务中多次读取同样记录的结果是一致的。可重复读隔离级别无法解决幻读的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。
可重复读时MySQL的默认事务隔离级别。
serializable(可串行化),serializable是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。
多版本并发控制
MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。不仅使MySQL,包括Oracle等其他数据库系统也都实现了MVCC,但各自的实现机制不尽相同,因为MVCC没有一个统一的实现标准。
可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。
MVCC的实现,是通过保存数据在某个时间点的快照来实现的。
前面说到不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制。
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号就会自动递增。
大家要有没有想过为什么MySQL数据库可以实现主从复制,实现持久化,实现回滚呢?其实关键在于MySQL里的三种log,分别是binlog、redo log、undo logo。
binlog:
binlog是用于记录数据库表结构和表数据变更的二进制日志,比如insert、update、delete、create等等操作,不会记录select、show操作,因为没有对数据本身发生变更。
binlog可以用来干什么?
第一,用于主从复制。一般在公司中做一主二从的结构时,就需要master节点打开binlog日志,从机订阅binlog日志的信息,因为binlog日志记录了数据库数据的变更,所以当master发生数据变更时,从机也能随着master节点的数据变更而变更,做到主从复制的效果。
第二,用于数据恢复。因为binlog记录了数据库的变更,所有可以用于数据恢复。Position是用于记录binlog日志的指针。当我们需要恢复数据时,只要指定--start-position和--stop-position那么就可以恢复指定区间的数据。
redo log
试想,假设需要修改的数据加载到内存中,并且修改成功了,但是还没来得及刷到磁盘中,这时数据库宕机了,那么这次修改成功后的数据就丢失了。
为了避免出现这种问题,MySQL引入了redo log。
我们在内存中对数据进行更新之后,更新完成后写入到redo log buffer中,然后由redo log buffer再写入到redo log file中。
redo log file记录着xxx页做了xxx修改,所以即使mysql发生宕机,也可以通过redo log进行数据恢复,也就是说在内存中更新成功后,即使没有刷新到磁盘中,但也不会因为宕机而导致数据丢失。
redo log是恢复在内存更新后,还没来得及刷到磁盘的数据。
binlog是存储所有数据变更的情况,理论上只要记录在binlog上的数据,都可以恢复。
undo log
undo log的作用主要用于回滚,mysql数据库的事务的原子性就是通过undo log实现的。
undo log主要存储的是数据的逻辑变化日志,比如说我们要insert
一条数据,那么undo log就会生成一条对应的delete日志。简单点说,undo log记录的是数据修改之前的数据,因为需要支持回滚。
那么当需要回滚时,只需要利用undo log的日志就可以恢复到修改前的数据。
undo log另一个作用是实现多版本控制(MVCC),undo记录中包含了记录更改前的镜像,如果更改数据的事务未提交,对于隔离级别大于等于read commit的事务而言,不应该返回更改后数据,而应该返回老版本的数据。
学完之后,我们知道这三种日志在mysql中都有着重要的作用,再回顾一下:
-
binlog主要用于复制和数据恢复。
-
redo log用于恢复在内存更新后,还没来得及刷到磁盘的数据。
-
undo log用于实现回滚和多版本控制。