Mysql知识点整理

事物隔离级别

查看当前事务隔离级别

1
select @@tx_isolation;

Read Uncommitted(未提交读)

1
set tx_isolation='READ-UNCOMMITTED';
  • 所有事务都可以看到其他未提交事务的执行结果
  • 本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少
  • 该级别引发的问题是——脏读(Dirty Read):读取到了未提交的数据

Read Committed(已提交读),可以读取到其他事物已经提交的数据

1
2
set tx_isolation='READ-COMMITTED';
set tx_isolation='REPEATABLE-READ';
  • 大多数数据库系统的默认隔离级别(但不是MySQL默认的)
  • 满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变
  • 该级别出现的问题是——不可重复读(Non repeatable Read):不可重复读意味着我们在同一个事务中执行完全相同的select语句时可能看到不一样的结果
  • 导致这种情况的原因可能有:
    • 有一个交叉的事务有新的commit,导致了数据的改变
    • 一个数据库被多个实例操作时,同一事务的其他实例在该实例处理其间可能会有新的commit

Repeatable Read(可重读)

  • MySQL的默认事务隔离级别
  • 它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行
  • 此级别可能出现的问题——幻读(Phantom Read):当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行(或者删除数据),当用户再读取该范围的数据行时,会发现有新的“幻影”
  • InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题

Serializable(可串行化)

– 最高的隔离级别
– 它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。
– 在这个级别,可能导致大量的超时现象和锁竞争

事务的ACID属性。

  • 原子性(Actomicity)
    事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行
  • 一致性(Consistent)
    在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以操持完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的
  • 隔离性(Isolation)
    数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然
  • 持久性(Durable)
    事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持

并发事务带来的问题

  • 更新丢失(Lost Update)-需要应用程序对要更新的数据加必要的锁来解决(乐观锁)
    当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了其他事务所做的更新
    例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档
    最后保存其更改保存其更改副本的编辑人员覆盖另一个编辑人员所做的修改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题
  • 脏读(Dirty Reads)
    一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制
    第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”
  • 不可重复读(Non-Repeatable Reads):一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”
  • 幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”

备注:“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决

数据库实现事务隔离的方式,基本可以分为以下两种

  • 对其加锁,阻止其他事务对数据进行修改
  • 通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取
    从用户的角度,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。
    在MVCC并发控制中,读操作可以分成快照读 (snapshot read)与当前读 (current read)。
    快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。
    当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。 InnoDB 就是基于多版本控制的引擎

Mysql 锁

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 MyISAM 默认
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 INODB 默认(有索引的前提下才会使用行锁)
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

MyISAM引擎锁

  • 表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)
  • 对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求
  • 对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作
  • MyISAM表的读操作与写操作之间,以及写操作之间是串行的;
  • MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁
  • MyISAM表显示加锁
1
2
3
lock tables test2 read local; --(只对MyISAM有效,锁定了可能还能insert 并发插入 )
lock tables test read;
lock tables user write;
  • MyISAM显示加锁的情况举例

例如, 有一个订单表orders,其中记录有各订单的总金额total,同时还有一个订单明细表order_detail,其中记录有各订单每一产品的金额小计 subtotal,假设我们需要检查这两个表的金额合计是否相符,可能就需要执行如下两条SQL

1
2
select sum(total) from orders;
select sum(subtotal) from order_detail

这时,如果不先给两个表加锁,就可能产生错误的结果,因为第一条语句执行过程中,order_detail表可能已经发生了改变。因此,正确的方法应该是:

1
2
3
4
Lock tables orders read local, order_detail read local;(需要一次获取所需的所有锁)
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
Unlock tables;

a、上面的例子在LOCK TABLES时加了“local”选项,其作用就是在满足MyISAM表并发插入条件的情况下,允许其他用户在表尾并发插入记录
b、在用LOCK TABLES给表显式加表锁时,必须同时取得所有涉及到表的锁(这也正是MyISAM表不会出现死锁(Deadlock Free)的原因),并且MySQL不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,
不能访问未加锁的表;

  • 查询表级锁争用情况
    可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺
    1
    show status like 'table%';
  • ————————–+——-+
    | Variable_name | Value |

+—————————-+——-+
| Table_locks_immediate | 2195 |
| Table_locks_waited | 0 |
| Table_open_cache_hits | 37 |
| Table_open_cache_misses | 1 |
| Table_open_cache_overflows | 0

  • 并发插入(Concurrent Inserts)在一定条件下,MyISAM表也支持查询和插入操作的并发进行
    MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。
    当concurrent_insert设置为0时,不允许并发插入。
    当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
    当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录

InnoDB锁

快照读和当前读

  • 快照读:简单的select操作,属于快照读,不加锁
    1
    select * from table where ?; 
  • 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
    下面语句都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。
    1
    2
    3
    4
    5
    select * from table where ? lock in share mode;
    select * from table where ? for update;
    insert into table values ;
    update table set ? where ?;
    delete from table where ?;

InnoDB的行锁模式及加锁方法

  • 共享锁(S锁、读锁)
    允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁(写锁),InnoDB引擎select语句默认不会加任何锁类型
    1
    select * from table where ? lock in share mode;
  • 排他锁(X锁、写锁)
    允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁, InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁
  • InnoDB还有两种内部使用的意向锁(Intention Locks),为了允许行锁和表锁共存,实现多粒度锁机制
    • 意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
    • 意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
  • innodb行级锁 record-level lock大致有三种
    • record lock 锁住某一行记录
    • gap lock 锁住某一段范围中的记录
    • next key lock 是前两者效果的叠加。

InnoDB行锁实现方式(索引加锁)

InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

binlog 及主从同步

binlog格式

  • statement:基于SQL语句的模式,某些语句和函数如UUID, LOAD DATA INFILE等在复制过程可能导致数据不一致甚至出错。
  • row:基于行的模式,记录的是行的变化,很安全。但是binlog会比其他两种模式大很多,在一些大表中清除大量数据时在binlog中会生成很多条语句,可能导致从库延迟变大。
  • mixed:混合模式,根据语句来选用是statement还是row模式。

主从同步流程

主从同步流程

  • Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容(change master to …….,start slave)
  • Master接收到来自Slave的IO进程的请求后,通过负责复制的IO进程根据请求信息读取制定日志指定位置之后的日志信息,返回给Slave 的IO进程。
    返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的bin-log文件的名称以及bin-log的位置
  • Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master端的 bin-log的文件名和位置记录到master-info文件中
  • Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在自身执行

从库必须设置为只读,防止误写数据,(root 用户还是可以写) read_only=1

Multi Versioning Concurrency Control(MVCC)

普通锁,本质是串行执行
读写锁,可以实现读读并发
数据多版本,可以实现读写并发

redo, undo,回滚段

InnoDB的内核,会对所有row数据增加三个内部属性:
(1)DB_TRX_ID,6字节,记录每一行最近一次修改它的事务ID;
(2)DB_ROLL_PTR,7字节,记录指向回滚段undo日志的指针;
(3)DB_ROW_ID,6字节,单调递增的行ID
(4)redo日志保证已提交事务的ACID特性,设计思路是,通过顺序写替代随机写,提高并发
(5)undo日志用来回滚未提交的事务,它存储在回滚段里
(6)InnoDB是基于MVCC的存储引擎,它利用了存储在回滚段里的undo日志,即数据的旧版本,提高并发

参考资料

更新过程

  • 对 DB_ROW_ID = 1 的这行记录加排他锁
  • 把该行原本的值拷贝到 undo log 中,DB_TRX_ID 和 DB_ROLL_PTR 都不动
  • 修改该行的值这时产生一个新版本,更新 DATA_TRX_ID为修改记录的事务 ID,将 DATA_ROLL_PTR 指向刚刚拷贝到 undo log 链中的旧版本记录,这样就能通过 DB_ROLL_PTR找到这条记录的历史版本。如果对同一行记录执行连续的 UPDATE,Undo Log 会组成一个链表,遍历这个链表可以看到这条记录的变迁
    记录 redo log,包括 undo log 中的修改。

read view

当前系统中的所有的活跃事务拷贝到一个列表生成ReadView

RR 下的 ReadView 生成

执行第一个SELECT语句时,后续所有的 SELECT 都是复用这个 ReadView

RC 下的 ReadView 生成

每个SELECT语句开始时,都会重新将当前系统中的所有的活跃事务拷贝到一个列表生成 ReadView

Mysql加锁过程详解(1)-基本知识
MySQL源码分析】浅谈Mysql的锁
淘宝mysql文档
MySQL InnoDB MVCC 机制的原理及实现
redo和undo日志