本节咱们假定参数binlog_row_image设置为‘FULL’也就是默认值。mysql
1、从一个列子出发算法
在开始以前咱们先假定参数‘slave_rows_search_algorithms’为默认值,即:sql
TABLE_SCAN,INDEX_SCANapp
由于这个参数会直接影响到对索引的利用方式。ide
咱们仍是以‘Delete’操做为例,实际上对于索引的选择‘Update’操做也是同样的,由于都是经过before_image去查找数据。我测试的表结构、数据和操做以下:函数
mysql> show create table tkkk \G性能
*************************** 1. row ***************************测试
Table: tkkkspa
Create Table: CREATE TABLE `tkkk` (debug
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
`c` int(11) DEFAULT NULL,
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> select * from tkkk;
+------+------+------+
| a | b | c |
+------+------+------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
| 4 | 4 | 4 |
| 5 | 5 | 5 |
| 6 | 6 | 6 |
| 7 | 7 | 7 |
| 8 | 8 | 8 |
| 9 | 9 | 9 |
| 10 | 10 | 10 |
| 11 | 11 | 11 |
| 12 | 12 | 12 |
| 13 | 13 | 13 |
| 15 | 15 | 15 |
| 15 | 16 | 16 |
| 15 | 17 | 17 |
+------+------+------+
16 rows in set (2.21 sec)
mysql> delete from tkkk where a=15;
Query OK, 3 rows affected (6.24 sec)
由于我作了debug索引这里时间看起来很长
对于这样一个‘Delete’语句来说主库会利用到索引 KEY a,删除的三条数据咱们实际上只须要一次索引的定位(参考btr_cur_search_to_nth_level函数),而后顺序扫描接下来的数据进行删除就能够了。大概的流程以下图:
这条数据删除的三条数据的before_image将会记录到一个DELETE_ROWS_EVENT中。从库应用的时候会从新评估应该使用哪一个索引,优先使用主键和惟一键。对于Event中的每条数据都须要进行索引定位操做,而且对于非惟一索引来说第一次返回的第一行数据可能并非删除的数据,还须要须要继续扫描下一行,在函数Rows_log_event::do_index_scan_and_update中有以下代码:
while (record_compare(m_table, &m_cols))//比较每个字段 若是不相等 扫描下一行
{
while((error= next_record_scan(false)))//扫描下一行
{
/* We just skip records that has already been deleted */
if (error == HA_ERR_RECORD_DELETED)
continue;
DBUG_PRINT("info",("no record matching the given row found"));
goto end;
}
}
这些代价是比主库更大的。在这个列子中没有主键和惟一键,所以依旧使用的是索引KEY a,大概流程以下图:
可是若是咱们在从库增长一个主键,那么在从库进行应用的时候流程以下:
咱们从上面的流程来看,主库‘Delete’操做和从库‘Delete’操做主要的区别在于:
从库每条数据都须要索引定位查找数据。
从库在某些状况下经过非惟一索引查找的数据第一条数据可能并非删除的数据,所以还须要继续进行索引定位和查找。
对于主库来说通常只须要一次数据定位查找便可,接下来访问下一条数据就行了。其实对于真正的删除操做来说并无太多的区别。若是合理的使用了主键和惟一键能够将上面提到的两点影响下降。在形成从库延迟的状况中,没有合理的使用主键和惟一键是一个比较重要的缘由。
最后若是表上一个索引都没有的话,那么状况变得更加严重,简单的图以下:
咱们能够看到每一行数据的更改都须要进行全表扫描,这种问题就很是严重了。这种状况使用参数‘slave_rows_search_algorithms’的HASH_SCAN选项也许能够提升性能,下面咱们就来进行讨论。
2、确认查找数据的方式
前面的例子中咱们接触了参数‘slave_rows_search_algorithms’,这个参数主要用于确认如何查找数据。其取值能够是下面几个组合(来自官方文档),源码中体现为一个位图:无锡妇科医院排行 http://www.0510bhyy.com/
TABLE_SCAN,INDEX_SCAN(默认值)
INDEX_SCAN,HASH_SCAN
TABLE_SCAN,HASH_SCAN
TABLE_SCAN,INDEX_SCAN,HASH_SCAN
在源码中有以下的说明,固然官方文档也有相似的说明:
/*
Decision table:
- I --> Index scan / search
- T --> Table scan
- Hi --> Hash over index
- Ht --> Hash over the entire table
|--------------+-----------+------+------+------|
| Index\Option | I , T , H | I, T | I, H | T, H |
|--------------+-----------+------+------+------|
| PK / UK | I | I | I | Hi |
| K | Hi | I | Hi | Hi |
| No Index | Ht | T | Ht | Ht |
|--------------+-----------+------+------+------|
*/
实际上源码中会有三种数据查找的方式,分别是:
ROW_LOOKUP_INDEX_SCAN
对应函数接口:Rows_log_event::do_index_scan_and_update
ROW_LOOKUP_HASH_SCAN
对应函数接口:Rows_log_event::do_hash_scan_and_update
ROW_LOOKUP_TABLE_SCAN
对应函数接口:Rows_log_event::do_table_scan_and_update
在源码中以下:
switch (m_rows_lookup_algorithm)//根据不一样的算法决定使用哪一个方法
{
case ROW_LOOKUP_HASH_SCAN:
do_apply_row_ptr= &Rows_log_event::do_hash_scan_and_update;
break;
case ROW_LOOKUP_INDEX_SCAN:
do_apply_row_ptr= &Rows_log_event::do_index_scan_and_update;
break;
case ROW_LOOKUP_TABLE_SCAN:
do_apply_row_ptr= &Rows_log_event::do_table_scan_and_update;
break;
决定如何查找数据以及经过哪一个索引查找正是经过参数‘slave_rows_search_algorithms’的设置和表中是否有合适的索引共同决定的,并非彻底由‘slave_rows_search_algorithms’参数决定。
下面这个图就是决定的过程,能够参考函数decide_row_lookup_algorithm_and_key(以下图)。
3、ROW_LOOKUP_HASH_SCAN方式的数据查找
总的来说这种方式和ROW_LOOKUP_INDEX_SCAN和ROW_LOOKUP_TABLE_SCAN都不一样,它是经过表中的数据和Event中的数据进行比对,而不是经过Event中的数据和表中的数据进行比对,下面咱们将详细描述这种方法。
假设咱们将参数‘slave_rows_search_algorithms’设置为INDEX_SCAN,HASH_SCAN,且表上没有主键和惟一键的话,那么上图的流程将会把数据查找的方式设置为ROW_LOOKUP_HASH_SCAN。
在ROW_LOOKUP_HASH_SCAN又包含两种数据查找的方式:
Hi --> Hash over index
Ht --> Hash over the entire table
对于ROW_LOOKUP_HASH_SCAN来说,其首先会将Event中的每一行数据读取出来存入到HASH结构中,若是可以使用到Hi那么还会额外维护一个集合(set),将索引键值存入集合,做为索引扫描的依据。若是没有索引这个集合(set)将不会维护直接使用全表扫描,即Ht。
须要注意一点这个过程的单位是Event,咱们前面说过一个DELETE_ROWS_EVENT可能包含了多行数据,Event最大为8K左右。所以使用Ht --> Hash over the entire table的方式,将会从原来的每行数据进行一次全表扫描变为每一个Event才进行一次全表扫描。
可是对于Hi --> Hash over index来说效果就没有那么明显了,由于若是删除的数据重复值不多的状况下,依然须要足够多的索引定位查找才行,可是若是删除的数据重复值较多那么构造的集合(set)元素将会大大减小,也就减小了索引查找定位的开销。
考虑另一种状况,若是个人每条delete语句一次只删除一行数据而不是delete一条语句删除大量的数据,那这种状况每一个DELETE_ROWS_EVENT只有一条数据存在,那么使用ROW_LOOKUP_HASH_SCAN方式并不会提升性能,由于这条数据仍是须要进行一次全表扫描或者索引定位才能查找到数据,和默认的方式没什么区别。
整个过程参考以下接口:
Rows_log_event::do_hash_scan_and_update:总接口,调用下面两个接口。
Rows_log_event::do_hash_row:将数据加入到hash结构,若是有索引还须要维护集合(set)。
Rows_log_event::do_scan_and_update:查找而且进行删除操做,会调用Rows_log_event::next_record_scan进行数据查找。
Rows_log_event::next_record_scan:具体的查找方式实现了Hi --> Hash over index和Ht --> Hash over the entire table的查找方式
下面咱们仍是用最开始的列子,咱们删除了三条数据,所以DELETE_ROW_EVENT中包含了三条数据。假设咱们参数‘slave_rows_search_algorithms’设置为INDEX_SCAN,HASH_SCAN。由于个人表中没有主键和惟一键,所以会最终使用ROW_LOOKUP_HASH_SCAN进行数据查找。可是由于咱们有一个索引key a,所以会使用到Hi --> Hash over index。为了更好的描述Hi和Ht两种方式,咱们也假定另外一种状况是表上一个索引都没有,我将两种方式放到一个图中方便你们发现不一样点,以下图:
4、总结
我记得之前有位朋友问我主库没有主键若是我在从库创建一个主键能下降延迟吗?这里咱们就清楚了答案是确定的,由于从库会根据Event中的行数据进行使用索引的选择。那么总结一下:
slave_rows_search_algorithms参数设置了HASH_SCAN并不必定会提升性能,只有知足以下两个条件才会提升性能:
从库索引的利用是自行判断的,顺序为主键->惟一键->普通索引。
若是slave_rows_search_algorithms参数没有设置HASH_SCAN,而且没有主键/惟一键那么性能将会急剧降低形成延迟。若是连索引都没有那么这个状况更加严重,由于更改的每一行数据都会引起一次全表扫描。
所以咱们发如今MySQL中强制设置主键又多了一个理由。