MySQL性能优化3-深入InnoDB

目录

事务

概念

ACID

并发一致性问题

事务四种隔离级别

InooDB引擎对隔离级别的支持程度

封锁粒度

封锁类型

共享锁(行锁):Shared Locks

排它锁(行锁):Exclusive Locks

思考:InooDB到底锁住了什么?

意向共享锁、意向排它锁(表锁):

自增锁:AUTO-INC Locks

记录锁 (Record Locks)、间隙锁 (Gap Locks)、临键锁 (Next-key Locks)

死锁介绍

MVCC

Undo log

快照读与当前读

Redo log

MVCC实现过程

配置优化

数据库表设计


事务

  • 概念

事务指的是满足ACID特性的一组操作,可以通过commit提交一个事务,也可以通过rollback回滚一个事务。事务就是访问数据库的一个操作序列,这些操作要么全部执行,要么全部执行。

MySQL性能优化3-深入InnoDB_第1张图片

  • ACID

1 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。事务的操作如果成功将全部应用到数据库中,如果操作失败则不能对数据库有任何影响。

2 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是一致的。即事务的执行使得数据库从一个正确得状态转换成另一个正确的状态。

3 隔离性(Isolation)
一个事务在最终提交之前,对其它事务是不可见的。

4 持久性(Durability)
一旦事务提交,其所做的修改将会永远保存到数据库中,即使系统发生崩溃,事务的执行也不能丢失。

事务的ACID特性概念简单,但不是很好理解,主要原因是因为这几个特性不是一种平级关系:

  • 只有满足一致性,事务的执行结果才是正确的。
  • 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。
  • 在并发的情况下,多个事务并发执行,事务不仅要满足原子性,还要满足隔离性,才能满足一致性。
  • 事务满足持久化是为了应对数据库崩溃的情况。

MySQL性能优化3-深入InnoDB_第2张图片

  • 并发一致性问题

在并发的情况下,事务的隔离性很难保证,因此会出现很多并发一致性问题。

  • 丢失修改

T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。

A、B两个事务都某个数据进行修改,A先修改,B后修改,那么B的修改就会覆盖A 的修改,A的修改就会丢失。

MySQL性能优化3-深入InnoDB_第3张图片

  • 读脏数据

T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。

A在对一个数据进行修改,B这个时候读到了修改后的数据,A又撤销了对数据的修改。

MySQL性能优化3-深入InnoDB_第4张图片

  • 不可重复度

T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。

事务A多次读取同一个数据,事务B在事务A多次读取的过程中,对数据做了更新并提交,导致事务A多次读取的结果不一致。

MySQL性能优化3-深入InnoDB_第5张图片

  • 幻影读

T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。

MySQL性能优化3-深入InnoDB_第6张图片

不可重复读和幻影读和容易混淆,不可重复读侧重于修改,幻影读侧重于新增或删除。解决不可重复度的问题只需要锁住满足条件的行,解决幻影读需要锁表。

产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性,并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务隔离级别,让用户以一种更轻松的方式处理并发一种性问题。

  • 事务四种隔离级别

未提交读(READ UNCOMMITTED)--未解决并发问题
事务中的修改,即使没有提交,对其它事务也是可见的。

提交读(READ COMMITTED)--解决脏读问题
一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。

可重复度(REPEATABLE READ)--解决不可重复度问题
保证在同一个事务中多次读取同样的数据结果是一样的。

可串行化(SERIALIZABLE)--解决所有问题

  • InooDB引擎对隔离级别的支持程度

MySQL性能优化3-深入InnoDB_第7张图片

  • 封锁粒度

Mysql中提供了两种封锁粒度:行级锁和表级锁。

应当尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。

但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。
表锁与行锁的区别:

锁定粒度:表锁 > 行锁

加锁效率:表锁 > 行锁

冲突概率:表锁 > 行锁

并发性能:表锁 < 行锁

InnoDB存储引擎支持行锁和表锁(另类的行锁)

  • 封锁类型

  • 共享锁(行锁):Shared Locks

又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁, 都能访问到数据,但是只能读不能修改;

  • 排它锁(行锁):Exclusive Locks

又称为写锁,简称X锁,排他锁不能与其他锁并存,如一个事务获取了一个数据行的排他 锁,其他事务就不能再获取该行的锁(共享锁、排他锁),只有该获取了排他锁的事务是可以对 数据行进行读取和修改,(其他事务要读取数据可来自于快照)

思考:InooDB到底锁住了什么?

InnoDB的行锁是通过给索引上的索引项加锁来实现的。只有通过索引条件进行数据检索,InnoDB才使用行级锁,否则,InnoDB 将使用表锁(锁住索引的所有记录)

  • 意向共享锁、意向排它锁(表锁):

使用意向锁可以更容易地支持多粒度封锁。在存在行级锁和表级锁地情况下,事务T想对表A加X锁,就需要先检查是否有其它事务对表A或者表A中地任意一行加了锁,那么就需要对表A地每一行检测一次,这是非常耗时的。

意向锁在原来的X/S锁之上引入了IX/IS锁,用来表示一个事务想要在表中的某个数据行上加上X锁或者S锁,有以下两个规定:

一个事务在获得某个数据行对象得S锁之前,必须先获得表得IS锁或者更强的锁。

一个事务在获得某个数据行对象的X锁之前,必须先获得表的IX锁。

通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。

理解:当一个事务要对一个数据行加S或X锁之前,都会对表加IS锁或IX锁,这样在想要对表进行X之前,就能很容易判断是否有数据在对表中的数据行进行X或者S操作。

任意 IS/IX 锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁;

S 锁只与 S 锁和 IS 锁兼容,也就是说事务 T 想要对数据行加 S 锁,其它事务可以已经获得对表或者表中的行的 S 锁。

  • 自增锁:AUTO-INC Locks

针对自增列自增长的一个特殊的表级别锁

show variables like 'innodb_autoinc_lock_mode';

默认取值1,代表连续,事务未提交ID永久丢失

  • 记录锁 (Record Locks)、间隙锁 (Gap Locks)、临键锁 (Next-key Locks)

  • Next-key Locks

锁住记录+区间(左开右闭)

当sql执行按照索引进行数据的检索时,查询条件为范围查找(between and、<>等)并有数据命中则此时SQL语句加上的锁为Next-key locks,锁住索引的记录+区间(左开右闭)

  • Gap locks:

锁住数据不存在的区间(左开右开)

当sql执行按照索引进行数据的检索时,查询条件的数据不存在,这时SQL语句加上的锁即为 Gap locks,锁住索引不存在的区间(左开右开)

  • Record locks:

锁住具体的索引项

当sql执行按照唯一性(Primary key、Unique key)索引进行数据的检索时,查询条件等值匹 配且查询的数据是存在,这时SQL语句加上的锁即为记录锁Record locks,锁住具体的索引项

  • 死锁介绍

多个并发事务(2个或者以上); 每个事务都持有锁(或者是已经在等待锁); 每个事务都需要再继续持有锁; 事务之间产生加锁的循环等待,形成死锁。

死锁是指两个或多个事务在同一个资源上相互占有,并且请求锁定对方占用的资源。从而导致恶性循环的现象。

为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。死锁检测,检测到死锁的循环依赖后返回一个错误;死锁超时,当查询的时间到达锁等待超时的设定后自动放弃锁请求。InnoDB处理死锁的方式是:将持有最少行级排它锁的事务进行回滚。

死锁避免:

1)类似的业务逻辑以固定的顺序访问表和行。

2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。

3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。

4)降低隔离级别,如果业务允许,将隔离级别调低也是较好的选择

5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁(或者说是表锁)

MVCC

多版本并发控制(Multi-Version Concurrency Control, MVCC)是MySQL的InnoDB存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用MVCC无法实现。MVCC是通过保存数据在某个事件节点的快照来实现的。可视为行锁的变种,非阻塞的读操作,写需要加X锁。

并发访问(读或写)数据库时,对正在事务内处理的数据做多版本的管理。以达到用来避免写操作的堵塞,从而引发读操 作的并发问题。

  • 版本号

系统版本号:是一个递增的数字,没开始一个新事务,系统版本号会自动递增。

事务版本号:事务开始时的系统版本号。

  • 隐藏的列

MVCC在每行记录后面都保存着两个隐藏的列,用来存储两个版本号:

创建版本号:指示创建一个数据行快照时的系统版本号

删除版本号:如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则就表示该快照已经被删除。

  • Undo log

undo log指事务开始之前,在操作任何数据之前,首先将需操作的数据备份到一个地方 (Undo Log)

UndoLog是为了实现事务的原子性而出现的产物

Undo Log实现事务原子性: 事务处理过程中如果出现了错误或者用户执行了 ROLLBACK语句,Mysql可以利用Undo Log中的备份 将数据恢复到事务开始之前的状态

Undo log实现多版本并发控制: 事务未提交之前,Undo保存了未提交之前的版本数据,Undo 中的数据可作为数据旧版本快照供 其他并发事务进行快照读

  • 快照读与当前读

快照度:SQL读取的数据是快照版本,也就是历史版本,普通的SELECT就是快照读 innodb快照读,数据的读取将由 cache(原本数据) + undo(事务修改过的数据) 两部分组成

当前读:SQL读取的数据是最新版本。通过锁机制来保证读取的数据无法通过其他事务进行修改 UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE都是当前读。当前读要避免并发问题要通过所机制。

理解:Undo log是为了保证事务的原子性,在事务开始之前将旧版本的数据进行备份。如果事务提交成功更新到undo log中,如果事务回滚,mysql就可以利用undo log中的数据恢复到事务开始之前的状态。对于多版本并发控制,读取的是undo log中的快照,可以解决脏读和不可重复度等并发问题。

  • Redo log

Redo log指事务中操作的任何数据,将最新的数据备份到一个地方 (Redo Log)

Redo log的持久: 不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo 中。具体 的落盘策略可以进行配置

RedoLog是为了实现事务的持久性而出现的产物

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

MySQL性能优化3-深入InnoDB_第8张图片

  • Redo log补充

指定Redo log 记录在{datadir}/ib_logfile1&ib_logfile2 可通过innodb_log_group_home_dir 配置指定目录存储

一旦事务成功提交且数据持久化落盘之后,此时Redo log中的对应事务数据记录就失去了意义,所 以Redo log的写入是日志文件循环写入的

  1. 指定Redo log日志文件组中的数量 innodb_log_files_in_group 默认为2
  2. 指定Redo log每一个日志文件最大存储量innodb_log_file_size 默认48M
  3. 指定Redo log在cache/buffer中的buffer池大小innodb_log_buffer_size 默认16M

Redo buffer 持久化Redo log的策略, Innodb_flush_log_at_trx_commit:

  1. 取值 0 每秒提交 Redo buffer --> Redo log OS cache -->flush cache to disk[可能丢失一秒内 的事务数据]
  2. 取值 1 默认值,每次事务提交执行Redo buffer --> Redo log OS cache -->flush cache to disk [最安全,性能最差的方式]
  3. 取值 2 每次事务提交执行Redo buffer --> Redo log OS cache 再每一秒执行 ->flush cache to disk操作
  • MVCC实现过程

1 SELECT
多个事务必须读取到同一个数据行的快照,并且这个快照是距离现在最近的一个有效快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致。

把没有对一个数据行做修改的事务称为 T,T 所要读取的数据行快照的创建版本号必须小于 T 的版本号,因为如果大于或者等于 T 的版本号,那么表示该数据行快照是其它事务的最新修改,因此不能去读取它。除此之外,T 所要读取的数据行快照的删除版本号必须大于 T 的版本号,因为如果小于等于 T 的版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。

2 INSERT
将当前系统版本号作为数据行快照的创建版本号。

3 DELETE
将当前系统版本号作为数据行快照的删除版本号。

4 UPDATE
将当前系统版本号作为更新前的数据行快照的删除版本号,并将当前系统版本号作为更新后的数据行快照的创建版本号。可以理解为先执行 DELETE 后执行 INSERT。先delete再insert

  • 配置优化

 

  • 基于参数的作用域:

全局参数 :

set global autocommit = ON/OFF;

会话参数(会话参数不单独设置则会采用全局参数) :

set session autocommit = ON/OFF;

注意:

  1. 全局参数的设定对于已经存在的会话无法生效
  2. 会话参数的设定随着会话的销毁而失效
  3. 全局类的统一配置建议配置在默认配置文件中,否则重启服务会导致配置失效
  • 寻找配置文件

mysql --help 寻找配置文件的位置和加载顺序

Default options are read from the following files in the given order: /etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf

mysql --help | grep -A 1 'Default options are read from the following files in the given order'

常见文件配置:

最大连接数配置 max_connections

系统句柄数配置 /etc/security/limits.conf ulimit -a mysql

句柄数配置 /usr/lib/systemd/system/mysqld.service

port = 3306

socket = /tmp/mysql.sock

basedir = /usr/local/mysql

datadir = /data/mysql

pid-file = /data/mysql/mysql.pid

user = mysql

bind-address = 0.0.0.0

max_connections=2000

lower_case_table_names = 0 #表名区分大小写

server-id = 1

tmp_table_size=16M

transaction_isolation = REPEATABLE-READ

ready_only=1

配置文件见ppt

  • 数据库表设计

  • 第一范式( 1NF):

字段具有原子性,不可再分。 所有关系型数据库系统都满足第一范式)数据库表中的字 段都是单一属性的, 不可再分;

  • 第二范式( 2NF): 要求实体的属性完全依赖于主键。 所谓完全依赖是指不能存在仅依赖主键一部分的属性, 如果存在, 那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体, 新实体与原 实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。 简而言之, 第二范式就是属性完全依赖主键。
  • 第三范式( 3NF): 满足第三范式( 3NF) 必须先满足第二范式( 2NF)。 简而言之, 第三范式( 3NF) 要求一个数据库表中不包含已在其它表中已包含的非主键信息。

简单一点:

1, 每一列只有一个单一的值,不可再拆分

2, 每一行都有主键能进行区分

3, 每一个表都不包含其他表已经包含的非主键信息。

充分的满足第一范式设计将为表建立太量的列 数据从磁盘到缓冲区,缓冲区脏页到磁盘进行持久的过程中,列的数量过多 会导致性能下降。过多的列影响转换和持久的性能

过分的满足第三范式化造成了太多的表关联 表的关联操作将带来额外的内存和性能开销

使用innodb引擎的外键关系进行数据的完整性保证 外键表中数据的修改会导致Innodb引擎对外键约束进行检查,就带来了额外 的开销

 

 

 

你可能感兴趣的:(知识点,数据库)