0%

一、MySQL数据库分布式事务

InnoDB引擎支持XA事务,并通过XA事务来支持分布式事务的实现。分布式事务是是指允许多个独立的事务资源(transactional resources)参与一个全局事务中,在使用分布式事务,InnoDB存储引擎的事务隔离级别必须 为serializable。
XA事务由一个或多个资源管理器(Resource Manager)、一个事务管理器(Transaction Manager)以及一个应用程序组成。
• 资源管理器:提供访问事务资源的方法。通常一个数据库就是一个资源管理器
• 事务管理器:协调参与全局事务中的各个事务,需要和参与全局事务的所有资源管理器通信。
• 应用程序:定义事务的边界,指定全局事务中的操作。
在MySQL数据库的分布式事务中,资源管理器就是MySQL数据库,事务管理器为连接MySQL的客户端。如下图:

innodb82

分布式事务使用两段式提交(two-phase commit)的方式。第一阶段,所有参与全局事务的节点开始准备 ,告诉事务管理器它们准备提交了。第二阶段,事务管理器告诉资源管理器执行的是commit或rollback操作。如果一个节点不能提交,则所有节点都都被告知需要回滚。
XA事务的语法:
XA {START|BEGIN} xid [JOIN|RESUME]
XA END xid[SUSPEND]FOR MIGRATE]]
XA PREPARE xid
XA COMMIT xid [ON PHASE]
XA ROLLBACK xid
XA RECOVER

通过查看参数innodb_support_xa确认是否支持XA事务,默认ON。

阅读全文 »

SQL标准定义的四种隔离级别是:
• READ UNCOMMITED
• READ COMMITED
• REPEATABLE READ
• SERIALIZABLE

InnoDB引擎的默认隔离级别是REPEATABLE READ,在next-key lock的算法下,因此避免了幻读的产生 ,因此,REPEATABLE READ级别已经保证了事务的隔离性要求,达到了SQL标准的SERIALIZABLE级别。

设置当前会话或全局的事务隔离级别:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL {READ UNCOMMITED|READ COMMITED|REPEATABLE READ|SERIALIZABLE}

如果想在mysqlf启动时设置,需要个性my.cnf文件,在[mysqld]加入一行:
[mysqld]
Transaction-isolation=READ-COMMITED

查看当前会话的隔离级别,用:
SELECT @@tx_isolation

阅读全文 »

一、purge

​ delete和update不会删除数据,因为MVCC机制的存在,可能数据还有别的事务在引用。因此delete和update的删除操作最终在purge线程中完成。
​ 为了节省存储空间,innodb的一个页上允许多个事务的undo log存在,后面事务产生的undo log总在最后面。
此外,innodb引擎还有一个history列表,它根据事务提交的顺序,将undo log进行链接。
例如下面的history列表,灰色代表还有事务引用。

innodb79

在执行purge过程中,innodb引擎会先从history列表中找到要清理的记录,这里为trx1,清理之后,innodb会继续在trx1的undo log所在的页继续寻找是否存在需要被清理的记录,这里找到trx3,接着找到trx5,但发现trx5被其它事务引用不能删除,故再次到history列表中查找,这次查找到trx2,接着在trx2的undo log所在的页找到trx6、trx4的记录删除,于是undo page 可以被重用。
动态参数innodb_purge_batch_size用来设置每次purge需要清理的undo page数量。在1.2版本前默认为20,1.2版本后默认为300。参数设置越大,每次回收的undo page越多,但设置太大,会使CPU和IO资源被占用太多影响数据库性能。
动态参数innodb_max_purge_lag用来控制history列表的长度,若长度大于该参数时,其会延缓DML的操作,默认为0,表示不会对history列表长度做任何控制。大于0时,就会延缓DML的操作,1.2版本引入innodb_max_purge_delay参数来控制delay的最大毫秒数。

二、group commit

阅读全文 »

一、undo的概念
事务在进行回滚时,就需要undo日志。因此,在对数据库进行修改时,innodb引擎不仅产生redo log,还会产生 一定量的undo log。
redo log存储在重做日志文件中,而undo log则是存在数据库内部的一个特殊段(segment)中。这个段称为undo 段(undo segment),undo段位于共享表空间里面。可以通过py_innnodb_info.py工具来查看共享表空间ibdata1中undo的数量。

Total number of page: 4864:
Insert Buffer Bitmap: 1
System Page: 135
Transaction system Page: 2
Freshly Allocated Page: 3661
Undo Log Page: 937
File Segment inode: 6
B-tree Node: 119
File Space Header: 3

可以看到undo的数量为937个。undo是逻辑日志,它只是逻辑上恢复数据库,并不是物理上把数据库恢复到原来的样子。除了回滚,undo日志还有个作用是MVCC。当用户读取一条记录时,若已经被其它事务占用,则当前事务可以通过undo读取之前的行版本信息,实现非锁定读。

二、undo存储管理
innodb引擎对undo的管理采用段的方式,引擎的每个回滚段(rollback segment)记录了1024个undo log segment,而在每个undo log segment中进行页的申请。共享表空间偏移量为5(0,5)的页记录了所有rollback segment header所在的页,这个页的类型为FIL_PAGE_TYPE_SYS。
1.2版本后,可以通过参数对rollback segment进行控制:
• Innodb_undo_directory 设置rollback segment文件所有的路径
• Innodb_undo_logs 设置rollback segment的个数
• Innodb_undo_tablespace 设置构成rollback segment文件的数量
Undo log segment分配页并写入undo log这个过程也需要写入重做日志,当事务提交时,innodb引擎会:
将undo log放到列表中,等待后面的purge操作;判断undo log所在的页是否可以重用,若可以,分配给下个事务使用。事务提交后不能马上删除undo log和undo log所在的页,因为有可能其它事务需要通过undo log来得到行记录之前的版本。什么时候删除undo log和undo log所在的页,由purge线程来确定。

三、undo log的格式
undo log分为insert undo log和update undo log两种格式,insert操作产生insert undo log,因为insert操作只对本事务可见,别的事务不可见,因此执行后可以删除undo 日志,不需要purge操作。
Insert undo log的格式如下图:

阅读全文 »

事务的隔离性由锁来实现,事务的原子性、一致性、持久性通过数据库的redo log和undo log来完成。Redo log称为重做日志 ,用来保证事务的原子性和持久性,undo log用来保证事务的一致性。redo和undo都可以认为是一种恢复操作,redo 恢复提交事务修改页的操作,而undo回滚行记录到特定的版本;redo通常是物理日志 ,记录的是页的物理修改操作,undo是逻辑日志 ,根据每行进行记录。

一、Redo的基本概念

​ 重做日志由两个部分组成:一是内存中的重做日志缓冲(redo log buffer),二是重做日志文件(redo log file)。
InnoDB引擎通过Force Log At Commit的机制实现事务的持久性,即当事务提交时,必须将该事务的所有日志写到重做日志文件进行持久化,这里的重做日志指redo log和undo log。redo日志基本按顺序写,而undo日志则需要随机写。
为了确保每次日志都写入重做日志,在每次将重做缓冲写入重做日志后,都需要调用一次fsync操作,fsync的效率决定于磁盘的性能,因此碰盘的性能决定事务提交的性能,也就是数据库的性能。
参数innodb_flush_log_at_trx_commit用来控制重做日志刷到磁盘的策略,默认值为1,表示事务事务提交时必须进行一次fsync操作。0代表事务提交时不会写入重做日志操作,这个操作只在master thread完成,而master thread每1秒会进行一次fsync操作。2表示事务提交时将重做日志写入重做日志文件,但只写入文件缓冲,不会进行fsync操作。这种情况下,mysql宕机而操作系统不宕机时,不会丢失事务。
关于innodb_flush_log_at_trx_commit参数设置对数据库性能影响的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE TABLE test_flushlog(
a INT ,
b VARCHAR(80)
) ENGINE=INNODB;

DELIMITER//
CREATE PROCEDURE p_test_flushlog(cnt INT UNSIGNED)
BEGIN
DECLARE s INT UNSIGNED DEFAULT 1;
DECLARE c CHAR(80) DEFAULT REPEAT('a',80);
WHILE s < cnt DO
INSERT INTO test_flushlog SELECT s,c;
COMMIT;
SET s=s+1;
END WHILE;
END;
//
DELIMITER;

CALL p_test_flushlog(50000);

在innodb_flush_log_at_trx_commit=1时,耗时1分13秒;
SET GLOBAL innodb_flush_log_at_trx_commit=0;
在innodb_flush_log_at_trx_commit=0时,耗时1.271秒;
SET GLOBAL innodb_flush_log_at_trx_commit=2;
在innodb_flush_log_at_trx_commit=2时,耗时8.625秒;

阅读全文 »

一、事务的特征

InnoDB引擎事务符合ACID原则:
• 原子性(atomicity):数据库事务是不可分割的操作单位。
• 一致性(consistency):事务将数据库从一种一致性状态变为下一种
• 隔离性(isolation):每个读写事务的对象和其它事务操作的对象能够相互分离,
• 持久性(durability):事务一旦提交,其结果就是永久性的。

二、事务的分类

​ • 扁平事务(Flat Transaction)
​ • 带有保存点的扁平事务(Flat Transaction with savepoints)
​ • 链事务(Chain Transaction)
​ • 嵌套事务(Nested Transaction)
​ • 分布式事务(Distributed Transaction)

扁平事务是最简单也是最常用的一种,操作是原子性的,要么执行,要么回滚。扁平事务不能提交或回滚事务的一部分。
带有保存点的扁平事务,允许事务在执行过程中,回滚到事务中较早的一个状态,
链事务,是保存点的事务的一种变种。带有保存点的事务,发生系统崩溃时,所有的保存点都会消失,因为它的保存点是易失的(volatile),而非持久的(persisten)。链事务的思想是:在提交事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开启的事务。提交事务操作和启动下一事务的操作合并为一个原子操作,这意味着下一个事务将看到上一个事务的执行结果。

阅读全文 »

一、脏读

脏读是指在不同的事务下,当前事务可以读取到另外事务未提交的数据。
以下例子说明了脏读发生的场景:

innodb55

脏读发生的条件是事务隔离级别为READ UNCOMMITED。

二、不可重复读

阅读全文 »

一、锁的3种算法

InnoDB引擎有3种行锁算法,分别是:

​ • Record Lock : 单个记录上锁;
​ • Gap Lock : 间隙锁,锁定一个范围,但不包含记录本身;
​ • Next-key Lock : Gap Lock+Record Lock,锁定一个范围并包含记录本身。
Record Lock总是会锁定索引记录,如果建表是没有设置索引,将使用隐式的主键来锁定。InnoDB对行的查询都是采用Next-key Lock算法。如果一个索引有10,11,13,20的值,那么可能锁定的区间为
(-无穷大,10],(10,11],(11,13],(13,20],(20,+无穷大)
使用Next-key Lock是为了解决幻读(Phantom Read)的问题。若事务T1已经锁定了(10,11],(11,13],如果插入记录12时,则锁定的范围会变为(10,11],(11,12],(12,13],当查询的索引有唯一属性时,会对Next-key Lock算法优化,降级为Record Lock,即锁住索引本身,而不是范围。

示例:
创建以下表:
CREATE TABLE t5(a INT PRIMARY KEY);
INSERT INTO t5 SELECT 1;
INSERT INTO t5 SELECT 2;
INSERT INTO t5 SELECT 5;
然后在会话A中执行以下SQL:
BEGIN;
SELECT * FROM t5 WHERE a=5 FOR UPDATE;
在会话B中执行以下SQL
BEGIN;
INSERT INTO t5 SELECT 4;
COMMIT;
可以看到顺利地插入了,因为a中主键唯一索引,锁的只是a=5这条记录;最后把会话A commit;
再看看辅助索引的情况:
执行以下SQL:
CREATE TABLE t6(a INT,b INT ,PRIMARY KEY(a),KEY(b));
INSERT INTO t6 SELECT 1,1;
INSERT INTO t6 SELECT 3,1;
INSERT INTO t6 SELECT 5,3;
INSERT INTO t6 SELECT 7,6;
INSERT INTO t6 SELECT 10,8;
在会话A中执行以下SQL:
BEGIN;
SELECT * FROM t6 WHERE a=3 FOR UPDATE;
此时有两个索引,需要分别锁定,对于聚集索引,用Record Lock锁定了列a=5的索引,对于辅助索引,加的是Next-key Lock,锁定的是范围(1,3),注意的是InnoDB还会对辅助索引的下一个键值加Gap Lock,即范围(3,6)也会被锁定,因此以下的SQL将会阻塞:
SELECT * FROM t6 WHERE a=5 LOCK IN SHARE MODE;
INSERT INTO t6 SELECT 4,2;
INSERT INTO t6 SELECT 6,5;
因为For Update加的是X锁;执行以下SQL是正常的插入,没有阻塞:
SELECT INTO t6 SELECT 8,6;
SELECT INTO t6 SELECT 2,0;
SELECT INTO t6 SELECT 6,7;

Gap Lock的作用是为防止并发时多个事务将记录插入到同一范围内,这样会导致幻像问题,如会话A中已经锁定了b=3的记录,如果没有Gap Lock锁定(3,6),那么用户可以插入索引为3的记录,这样会导致会话A的用户再次执行时,会返回不同的记录,导致幻像问题。
注意的是
可以通过以下的方法关闭Gap Lock
• 把事务隔离级别设置为READ COMMITED;
• 将参数innodb_locks_unsafe_for_binlog设置为1;

阅读全文 »

一、一致性非锁定读

​ 一致性非锁定读(consistent nonlocking read)是指InnoDB引擎通过行多版本控制(multi versioning)的方式来当前执行时间数据库中的行。如果读取的行正在执行update/delete操作,此时读操作不会等待锁的释放,而是读取了行的快照。

innodb46

该方案是通过Undo段来实现的。Undo 段主要用于事务中回滚数据。快照数据就是当前行数据的历史版本,每个记录可以有多个版本。由此带来的并发控制,称为多版本并发控制(Multi Version Concrrency Controll,MVCC).

在事务隔离级别为READ COMMITED和REPEATABLE READ的事务隔离级别下,InnoDB引擎使用的是一致性非 锁定读。在READ COMMIT隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新一份数据快照;而REPEATABLE READ级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。

阅读全文 »

一、Latch和Lock的区别

​ Latch称为闩锁,轻量级的锁,要求锁定的时间 非常短。latch在InnoDB中又分为mutext(互斥量)和rwlock(读写锁)。通常没有死锁检测机制。
​ Lock的对象是事务,锁定的是数据库中的对象,如表、页、行。一般lock对象只有在事务commit和rollback时才会释放。Lock是有死锁机制的。

innodb40

通过SHOW ENGINE INNODB MUTEX可以查看latch的情况:

innodb41

二、锁的类型

阅读全文 »