MySQL· 5.7优化·Metadata Lock子系统的优化

背景

引入MDL锁的目的,最初是为了解决著名的bug#989,在MySQL 5.1及之前的版本,事务执行过程中并不维护涉及到的所有表的Metatdata 锁,极易出现复制中断,例如如下执行序列:

Session 1: BEGIN;
Session 1: INSERT INTO t1 VALUES (1);
Session 2: Drop table t1; --------SQL写入BINLOG
Session 1: COMMIT; -----事务写入BINLOG

在备库重放 binlog时,会先执行DROP TABLE,再INSERT数据,从而导致复制中断。

在MySQL 5.5版本里,引入了MDL, 在事务过程中涉及到的所有表的MDL锁,直到事务结束才释放。这意味着上述序列的DROP TABLE 操作将被Session 1阻塞住直到其提交。

不过用过
5.5的人都知道,MDL实在是个让人讨厌的东西,相信不少人肯定遇到过在使用mysqldump做逻辑备份时,由于需要执行FLUSH TABLES
WITH READ LOCK (以下用FTWRL缩写代替)来获取全局GLOBAL的MDL锁,因此经常可以看到“wait for global
read
lock”之类的信息。如果备库存在大查询,或者复制线程正在执行比较漫长的DDL,并且FTWRL被block住,那么随后的QUERY都会被
block住,导致业务不可用引发故障。

为了解决这个问题,Facebook为MySQL增加新的接口替换掉FTWRL 只创建一个read view ,并返回与read view一致的binlog位点;另外Percona Server也实现了一种类似的办法来绕过FTWRL,具体点击文档连接以及percona的博客,不展开阐述。

MDL解决了bug#989,却引入了一个新的热点,所有的MDL锁对象被维护在一个hash对象中;对于热点,最正常的想法当然是对其进行分区来分散热点,不过这也是Facebook的大神Mark Callaghan在report了bug#66473后才加入的,当时Mark观察到MDL_map::mutex的锁竞争非常高,进而推动官方改变。因此在MySQL
5.6.8及之后的版本中,引入了新参数metadata_locks_hash_instances来控制对mdl hash的分区数(Rev:4350);

不过故事还没结束,后面的测试又发现哈希函数有问题,somedb. someprefix1 … .somedb .someprefix8
的hash
key值相同,都被hash到同一个桶下面了,相当于hash分区没生效。这属于hash算法的问题,喜欢考古的同学可以阅读下bug#66473后面
Dmitry Lenev的分析。

Mark进一步的测试发现Innodb的hash计算算法比my_hash_sort_bin要更高效, Oracle的开发人员重开了个bug#68487来跟踪该问题,并在MySQL5.6.15对hash key计算函数进行优化,包括fix 上面说的hash计算问题(Rev:5459),使用MurmurHash3算法来计算mdl
key的hash值。

MySQL 5.7 对MDL锁的优化

在MySQL 5.7里对MDL子系统做了更为彻底的优化。主要从以下几点出发:

第一,尽管对MDL HASH进行了分区,但由于是以表名+库名的方式作为key值进行分区,如果查询或者DML都集中在同一张表上,就会hash到相同的分区,引起明显的MDL HASH上的锁竞争。

针对这一点,引入了LOCK-FREE的HASH来存储MDL_lock,LF_HASH无锁算法基于论文"Split-Ordered
Lists: Lock-Free Extensible Hash Tables",实现还比较复杂。
注:实际上LF_HASH很早就被应用于Performance Schema,算是比较成熟的代码模块。由于引入了LF_HASH,MDL HASH分区特性自然直接被废除了 。对应WL#7305,
PATCH(Rev:7249)

第二,从广泛使用的实际场景来看,DML/SELECT相比DDL等高级别MDL锁类型,是更为普遍的,因此可以针对性的降低DML和SELECT操作的MDL开销。

为了实现对DML/SELECT的快速加锁,使用了类似LOCK-WORD的加锁方式,称之为FAST-PATH,如果FAST-PATH加锁失败,则走SLOW-PATH来进行加锁。

每个MDL锁对象(MDL_lock)都维持了一个long
long类型的状态值来标示当前的加锁状态,变量名为MDL_lock::m_fast_path_state
举个简单的例子:(初始在sbtest1表上对应MDL_lock::m_fast_path_state值为0)

Session 1: BEGIN;
Session 1: SELECT * FROM sbtest1 WHERE id =1; //m_fast_path_state = 1048576, MDL ticket 不加MDL_lock::m_granted队列
Session 2: BEGIN;
Session 2: SELECT * FROM sbtest1 WHERE id =2; //m_fast_path_state=1048576+1048576=2097152,同上,走FAST PATH
Session 3: ALTER TABLE sbtest1 ENGINE = INNODB;
//DDL请求加的MDL_SHARED_UPGRADABLE类型锁被视为unobtrusive
lock,可以认为这个是比上述SQL的MDL锁级别更高的锁,并且不相容,因此被强制走slow path。而slow
path是需要加MDL_lock::m_rwlock的写锁。m_fast_path_state = m_fast_path_state |
MDL_lock::HAS_SLOW_PATH | MDL_lock::HAS_OBTRUSIVE
注:DDL还会获得库级别的意向排他MDL锁或者表级别的共享可升级锁,但为了表述方便,这里直接忽略了,只考虑涉及的同一个MDL_lock锁对象。
Session 4: SELECT * FROM sbtest1 WHERE id =3; // 检查m_fast_path_state &HAS_OBTRUSIVE,如果DDL还没跑完,就会走slow path。

从上面的描述可以看出,MDL子系统显式的对锁类型进行了区分(OBTRUSIVE or
UNOBTRUSIVE),存储在数组矩阵m_unobtrusive_lock_increment。
因此对于相容类型的MDL锁类型,例如DML/SELECT,加锁操作几乎没有任何读写锁或MUTEX开销。对应WL#7304, WL#7306 , PATCH(Rev:7067,Rev:7129)(Rev:7586)

第三,由于引入了MDL锁,实际上早期版本用于控制Server和引擎层表级并发的THR_LOCK 对于Innodb而言已经有些冗余了,因此Innodb表完全可以忽略这部分的开销。

不过在已有的逻辑中,Innodb依然依赖THR_LOCK来实现LOCK TABLE tbname READ,因此增加了新的MDL锁类型来代替这种实现。实际上代码的大部分修改都是为了处理新的MDL类型,Innodb的改动只有几行代码。对应WL#6671,PATCH(Rev:8232)

第四,Server层的用户锁(通过GET_LOCK函数获取)使用MDL来重新实现。

用户可以通过GET_LOCK()来同时获取多个用户锁,同时由于使用MDL来实现,可以借助MDL子系统实现死锁的检测。注意由于该变化,导致用户锁的命名必须小于64字节,这是受MDL子系统的限制导致。对应WL#1159, PATCH(Rev:8356)


MySQL·性能优化·hash_scan 算法的实现解析

问题描述

首先,我们执行下面的TestCase:

  1. --source include/master-slave.inc
  2. --source include/have_binlog_format_row.inc
  3. connection slave;
  4. set global slave_rows_search_algorithms='TABLE_SCAN';
  5. connection master;
  6. create table t1(id int, name varchar(20);
  7. insert into t1 values(1,'a');
  8. insert into t2 values(2, 'b');
  9. ......
  10. insert into t3 values(1000, 'xxx');
  11. delete from t1;
  12. ---source include/rpl_end.inc

随着 t1 数据量的增大,rpl_hash_scan.test 的执行时间会随着 t1 数据量的增大而快速的增长,因为在执行 'delete
from t1;' 对于t1的每一行删除操作,备库都要扫描t1,即全表扫描,如果 select count(*) from t1 = N,
则需要扫描N次 t1 表, 则读取记录数为: O(N + (N-1) + (N-2) + .... + 1) = O(N^2),在
replication 没有引入 hash_scan,binlog_format=row时,对于无索引表,是通过
table_scan 实现的,如果一个update_rows_log_event/delete_rows_log_event
包含多行修改时,每个修改都要进行全表扫描来实现,其 stack 如下:

  1. #0 Rows_log_event::do_table_scan_and_update
  2. #1 0x0000000000a3d7f7 in Rows_log_event::do_apply_event
  3. #2 0x0000000000a28e3a in Log_event::apply_event
  4. #3 0x0000000000a8365f in apply_event_and_update_pos
  5. #4 0x0000000000a84764 in exec_relay_log_event
  6. #5 0x0000000000a89e97 in handle_slave_sql (arg=0x1b3e030)
  7. #6 0x0000000000e341c3 in pfs_spawn_thread (arg=0x2b7f48004b20)
  8. #7 0x0000003a00a07851 in start_thread () from /lib64/libpthread.so.0
  9. #8 0x0000003a006e767d in clone () from /lib64/libc.so.6

这种情况下,往往会造成备库延迟,这也是无索引表所带来的复制延迟问题。

如何解决问题:

  1. RDS 为了解这个问题,会在每个表创建的时候检查一下表是否包含主建或者唯一建,如果没有包含,则创建一个隐式主建,此主建对用户透明,用户无感,相应的show create, select * 等操作会屏蔽隐式主建,从而可以减少无索引表带来的影响;
  2. 官
    方为了解决这个问题,在5.6.6 及以后版本引入参数 slave_rows_search_algorithms ,用于指示备库在
    apply_binlog_event时使用的算法,有三种算法TABLE_SCAN,INDEX_SCAN,HASH_SCAN,其中
    table_scan与index_scan是已经存在的,本文主要研究HASH_SCAN的实现方式,关于参数
    slave_rows_search_algorithms的设置。

hash_scan 的实现方法:

简单的讲,在 apply rows_log_event时,会将 log_event 中对行的更新缓存在两个结构中,分别是:m_hash,
m_distinct_key_list。 m_hash:主要用来缓存更新的行记录的起始位置,是一个hash表;
m_distinct_key_list:如果有索引,则将索引的值push
到m_distinct_key_list,如果表没有索引,则不使用这个List结构; 其中预扫描整个调用过程如下:
Log_event::apply_event

  1. Rows_log_event::do_apply_event
  2. Rows_log_event::do_hash_scan_and_update
  3. Rows_log_event::do_hash_row  (add entry info of changed records)
  4. if (m_key_index < MAX_KEY) (index used instead of table scan)
  5. Rows_log_event::add_key_to_distinct_keyset ()

当一个event 中包含多个行的更改时,会首先扫描所有的更改,将结果缓存到m_hash中,如果该表有索引,则将索引的值缓存至m_distinct_key_list List 中,如果没有,则不使用这个缓存结构,而直接进行全表扫描;

执行 stack 如下:

  1. #0 handler::ha_delete_row
  2. #1 0x0000000000a4192b in Delete_rows_log_event::do_exec_row
  3. #2 0x0000000000a3a9c8 in Rows_log_event::do_apply_row
  4. #3 0x0000000000a3c1f4 in Rows_log_event::do_scan_and_update
  5. #4 0x0000000000a3c5ef in Rows_log_event::do_hash_scan_and_update
  6. #5 0x0000000000a3d7f7 in Rows_log_event::do_apply_event
  7. #6 0x0000000000a28e3a in Log_event::apply_event
  8. #7 0x0000000000a8365f in apply_event_and_update_pos
  9. #8 0x0000000000a84764 in exec_relay_log_event
  10. #9 0x0000000000a89e97 in handle_slave_sql
  11. #10 0x0000000000e341c3 in pfs_spawn_thread
  12. #11 0x0000003a00a07851 in start_thread ()
  13. #12 0x0000003a006e767d in clone ()

执行过程说明:

Rows_log_event::do_scan_and_update

  1. open_record_scan()
  2. do
  3. next_record_scan()
  4. if (m_key_index > MAX_KEY)
  5. ha_rnd_next();
  6. else
  7. ha_index_read_map(m_key from m_distinct_key_list)
  8. entry= m_hash->get()
  9. m_hash->del(entry);
  10. do_apply_row()
  11. while (m_hash->size > 0);

从执行过程上可以看出,当使用hash_scan时,只会全表扫描一次,虽然会多次遍历m_hash这个hash表,但是这个扫描是O(1),所以,代价很小,因此可以降低扫描次数,提高执行效率。

hash_scan 的一个 bug

bug详情:http://bugs.mysql.com/bug.php?id=72788
bug原因:m_distinct_key_list 中的index key 不是唯一的,所以存在着对已经删除了的记录重复删除的问题。
bug修复:http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8494

问题扩展:

  • 在没有索引的情况下,是不是把 hash_scan 打开就能提高效率,降低延迟呢?不一定,如果每次更新操作只一条记录,此时仍然需要全表扫描,并且由于entry 的开销,应该会有后退的情况;
  • 一个event中能包含多少条记录的更新呢?这个和表结构以及记录的数据大小有关,一个event 的大小不会超过9000 bytes, 没有参数可以控制这个size;
  • hash_scan 有没有限制呢?hash_scan 只会对更新、删除操作有效,对于binlog_format=statement 产生的 Query_log_event 或者binlog_format=row 时产生的 Write_rows_log_event 不起作用;

TokuDB·版本优化·7.5.0

TokuDB 7.5.0大版本已发布,是一个里程碑的版本,这里谈几点优化,以飨存储引擎爱好者们。

a) shutdown加速

有用户反馈TokuDB在shutdown的时候,半个小时还没完事,非常不可接受。在shutdown的时候,TokuDB在干什么呢?在做checkpoint,把内存中的节点数据序列化并压缩到磁盘。

那为什么如此耗时呢?如果tokudb_cache_size开的比较大,内存中的节点会非常多,在shutdown的时候,大家都排队等着被压缩到磁盘(串行的)。

在7.5.0版本,TokuDB官方针对此问题进行了优化,使多个节点并行压缩来缩短时间。

BTW: TokuDB在早期设计的时候已保留并行接口,只是一直未开启。

b) 内节点读取加速

在内存中,TokuDB内节点(internal node)的每个message buffer都有2个重要数据结构:

1) FIFO结构,保存{key, value}
 2) OMT结构,保存{key, FIFO-offset}

由于FIFO不具备快速查找特性,就利用OMT来做快速查找(根据key查到value)。这样,当内节点发生cache miss的时候,索引层需要做:

1) 从磁盘读取节点内容到内存
 2) 构造FIFO结构
 3) 根据FIFO构造OMT结构(做排序)

由于TokuDB内部有不少性能探(ji)针(shu),他们发现步骤3)是个不小的性能消耗点,因为每次都要把message
buffer做下排序构造出OMT,于是在7.5.0版本,把OMT的FIFO-offset(已排序)也持久化到磁盘,这样排序的损耗就没了。

c) 顺序写加速

当写发生的时候,会根据当前的key在pivots里查找(二分)当前写要落入哪个mesage buffer,如果写是顺序(或局部顺序,数据走向为最右边路径)的,就可以避免由"查找"带来的额外开销。

如何判断是顺序写呢?TokuDB使用了一种简单的启发式方法(heurstic):seqinsert_score积分式。如果:

1) 当前写入落入最右节点,对seqinsert_score加一分(原子)
2) 当前写入落入非最右节点,对seqinsert_score清零(原子)

当seqinsert_score大于100的时候,就可以认为是顺序写,当下次写操作发生时,首先与最右的节点pivot进行对比判断,如果确实为顺序写,则会被写到该节点,省去不少compare开销。方法简单而有效。


MariaDB· 性能优化·filesort with small LIMIT optimization

从MySQL 5.6.2/MariaDB 10.0.0版本开始,MySQL/MariaDB针对"ORDER BY ...LIMIT
n"语句实现了一种新的优化策略。当n足够小的时候,优化器会采用一个容积为n的优先队列来进行排序,而不是排序所有数据然后取出前n条。
这个新算法可以这么描述:(假设是ASC排序)

  1. 建立一个只有n个元素的优先队列(堆),根节点为堆中最大元素
  2. 根据其他条件,依次从表中取出一行数据
  3. 如果当前行的排序关键字小于堆头,则把当前元素替换堆头,重新Shift保持堆的特性
  4. 再取一条数据重复2步骤,如果没有下一条数据则执行5
  5. 依次取出堆中的元素(从大到小排序),逆序输出(从小到大排序),即可得ASC的排序结果

这样的算法,时间复杂度为m*log(n),m为索引过滤后的行数,n为LIMIT的行数。而原始的全排序算法,时间复杂度为m*log(m)。只要n远小于m,这个算法就会很有效。

不过在MySQL 5.6中,除了optimizer_trace,没有好的方法来看到这个新的执行计划到底起了多少作用。MariaDB 10.013开始,提供一个系统状态,可以查看新执行计划调用的次数:

Sort_priority_queue_sorts
 描述: 通过优先队列实现排序的次数。(总排序次数=Sort_range+Sort_scan)
 范围: Global, Session
 数据类型: numeric
 引入版本: MariaDB 10.0.13

此外,MariaDB还将此信息打入了Slow Log中。只要指定 log_slow_verbosity=query_plan,就可以在Slow Log中看到这样的记录:

  1. # Time: 140714 18:30:39
  2. # User@Host: root[root] @ localhost []
  3. # Thread_id: 3  Schema: test  QC_hit: No
  4. # Query_time: 0.053857  Lock_time: 0.000188  Rows_sent: 11  Rows_examined: 100011
  5. # Full_scan: Yes  Full_join: No  Tmp_table: No  Tmp_table_on_disk: No
  6. # Filesort: Yes  Filesort_on_disk: No  Merge_passes: 0  Priority_queue: Yes
  7. SET timestamp=1405348239;SET timestamp=1405348239;
  8. select * from t1 where col1 between 10 and 20 order by col2 limit 100;

"Priority_queue: Yes" 就表示这个Query利用了优先队列的执行计划(pt-query-digest 目前已经可以解析 Priority_queue 这个列)

淘宝内部分享:MySQL & MariaDB性能优化 【转】的更多相关文章

  1. 淘宝内部分享:MySQL & MariaDB性能优化

    发表于2015-01-20 16:26| 28875次阅读| 来源mysql.taobao.org| 22 条评论| 作者淘宝数据库团队 MySQL性能优化淘宝数据库 摘要:MySQL是目前使用最多的 ...

  2. 淘宝内部分享:怎么跳出MySQL的10个大坑

    编者按:淘宝自从2010开始规模使用MySQL,替换了之前商品.交易.用户等原基于IOE方案的核心数据库,目前已部署数千台规模.同时和Oracle, Percona, Mariadb等上游厂商有良好合 ...

  3. [转载] 淘宝内部分享:怎么跳出MySQL的10个大坑(上)

    原文: http://mp.weixin.qq.com/s?__biz=MzAxNjAzMTQyMA==&mid=209773318&idx=1&sn=e9600d3db80b ...

  4. 15 个有用的 MySQL/MariaDB 性能调整和优化技巧(转载的一篇好文)

    MySQL 是一个强大的开源关系数据库管理系统(简称 RDBMS).它发布于 1995 年(20年前).它采用结构化查询语言(SQL),这可能是数据库内容管理中最流行的选择.最新的 MySQL 版本是 ...

  5. 【MySQL】15个有用的MySQL/MariaDB性能调整和优化技巧

    MySQL 是一个强大的开源关系数据库管理系统(简称 RDBMS).它发布于 1995 年(20年前).它采用结构化查询语言(SQL),这可能是数据库内容管理中最流行的选择.最新的 MySQL 版本是 ...

  6. 15 个有用的 MySQL/MariaDB 性能调整和优化技巧

    MySQL 是一个强大的开源关系数据库管理系统(简称 RDBMS).它发布于 1995 年(20年前).它采用结构化查询语言(SQL),这可能是数据库内容管理中最流行的选择.最新的 MySQL 版本是 ...

  7. 1229【MySQL】性能优化之 Index Condition Pushdown

    转自http://blog.itpub.net/22664653/viewspace-1210844/  [MySQL]性能优化之 Index Condition Pushdown2014-07-06 ...

  8. mysql数据库性能优化(包括SQL,表结构,索引,缓存)

    优化目标减少 IO 次数IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑,当 ...

  9. 淘宝内部大量使用的开源系统监控工具--Tsar

    Tsar是淘宝开发的一个非常好用的系统监控工具,在淘宝内部大量使用    它不仅可以监控CPU.IO.内存.TCP等系统状态,也可以监控Apache,Nginx/Tengine,Squid等服务器状态 ...

随机推荐

  1. linux安装wifi驱动,开热点

    本次安装的debian系统安装的时候提示wifi硬件需要安装非自由固件才能运行,并告诉本硬件要安装的固件名字叫做iwlwifi-2030-6.ucode.是iwlwifi驱动适配我的wireless硬 ...

  2. DVI与VGA有什么区别

    [DVI与VGA有什么区别] DVI接口的传输信号采用全数字格式,与之对应的是采用模拟信号的VGA接口. VGA和DVI的区别,首先VGA模拟信号的传输比较麻烦,首先是将电脑内的数字信号转换为模拟信号 ...

  3. 【HDU5361】In Touch

    题意有n个人住在一条直线上,从左到右编号为1,2,3....n                                                                     ...

  4. 高性能Web服务器Nginx的配置与部署研究(15)Upstream负载均衡模块

    Nginx 的 HttpUpstreamModule 提供对后端(backend)服务器的简单负载均衡.一个最简单的 upstream 写法如下: server backend1.example.co ...

  5. Ubuntu Server 12.04 LTS搭建SVN服务及修改端口

    采用了apache结合svn的方式. 首先安装apache.subversion.svn-apache sudo apt-get install apache2 sudo apt-get instal ...

  6. SQL 数据库 学习 003 什么是数据库? 为什么需要数据库?是不是所有的软件都是用Sql Server?

    什么是数据库? 为什么需要数据库? 是不是所有的软件都是用Sql Server? 我的电脑系统: Windows 10 64位 使用的SQL Server软件: SQL Server 2014 Exp ...

  7. 基于weui的一个小插件

    移动端项目当中大量的使用了weui,为了减少工作量,方便修改,自己写了个小插件,暂时只有toast和dialog部分,可能会更新actionSheet等其他部分 更新一个手机端预览的二维码,就直接放项 ...

  8. ACM暑假培训宣讲稿

    (鞠躬)感谢大家的掌声! 我上台来作这次的宣讲,首先要感谢大家的捧场(当然,这是一句玩笑话),其实吧,我要感谢一下我们ACM班老大(班长),hjh队友,是他指派我来的,给了我这个宝贵的机会.最要感谢的 ...

  9. MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error

    今天购物车突然不能添加了,发现redis报错了,重启了一下好了,一会又报错了. 错误信息: MISCONF Redis is configured to save RDB snapshots, but ...

  10. mongoTemplate查询

    1.精确查询用“is” Criteria criteria=new Criteria("namespaceName"); criteria.is(namespaceName); Q ...