MySQL体系中,实现事务的责任大部分落在存储引擎上。
事务日志细节、行或页锁、实现事务隔离级别、提交与回滚、以及其他事务关键属性基本都是在存储引擎级别实现,所以,存储引擎跟SQL层交互需要使用统一的接口。
存储引擎事务实现综述
集成第三方存储引擎,需要定义两部分:定义与实现handler子类以及handlerton实现
不可避免,事务的实现一定需要实现handler子类的大量虚拟函数,但是事务实现的核心工作实在handlerton函数里面实现。可以这么理解:handler子类从概念上面与特定的表实例,而handlerton函数集却是与线程/连接相关。所以如COMMIT、ROLLBACK、SAVEPOINT等操作适合集成到handlerton。
事务本质实现方式完全取决于存储引擎。
由于与存储引擎交互是统一的接口协议,核心SQL不会了解到底层存储引擎的不同。
集成事务型存储引擎,存在很多因素需要考虑:
1、与非事务型甚至其他存储引擎类型的表交互问题
2、与查询缓存交互
3、与binlog交互
4、如何避免死锁
InnoDB提供了很好的范例,见sql/ha_innodb.cc文件,会注意到如何解决这些集成交互问题的。
>>实现handler子类
1、handler::has_transactions(). 告诉SQL层是否支持事务。
2、handler::start_stmt()/handler::external_lock()
两者用来让事务型存储引擎启动一个事务
external_lock() 在表实例被初始化都会被调用来开始事务处理,这与之前设计用来阻止表被外部应用修改的用途已经相去甚远。
但如果 使用LOCK TABLES人为锁定表时,handler::start_stmt()会被调用。
3、事务型存储引擎需要维护数据结构来跟踪当前事务的状态。THD结构中存在一个事务结构指针,指向ha_data数组。每个存储引擎在这个数组中都存在一个固定位移值,该位移值自动生成,并记录在handlerton的slot成员变量中。该变量在事务开始的时候被初始化,如InnoDB:
thd->ha_data[innobase_hton.slot] = trx;
4、事务开始后,存储引擎需要通过调用tran_register_ha()在核心SQL层注册。以便给SQL层留通知入口。InnoDB中,注册动作在两个函数里面实现:innobase_register_stmt()和innobase_register_trx_and_stmt().
innobase_register_stmt()调用trans_register_ha(thd,FALSE,&innobase_hton);
innobase_register_trx_and_stmt()调用trans_register_ha(thd,TRUE,&innobase_hton);
第二个参数设置为false,表示只是注册事务的当前语句;如果设置为true,表示注册整个事务.
注册的目的是为了协助COMMIT和ROLLBACK操作,当处理这些操作时,核心server代码需要定位到handlerton以便调用特定的存储引擎来通知底层.
5、启动事务两种方式:内部隐式启动和外部显式启动。
外部显式启动:当client调用BEGIN或者START TRANSACTION.
内部隐式启动:发起一个查询,事务型存储引擎内部默认启动事务.
当外部显式启动事务时,SQL层能够控制和记录存储引擎的当前运行状态。
而内部自动启动事务,SQL层没有控制机会,所以需要通过事务注册流程来通知上面SQL层。
6、handler::try_semi_consistent_read(), handler::was_semi_consistent_read()和handler::unlock_row()用于避免在update和delete请求中发生锁等待。
InnoDB从handler类继承,定义在sql/ha_innodb.cc。
需要大量的工作来转换原生InnoDB数据结构与MySQL上层数据格式的差异,因为上层数据格式设计大多数原来是为MyISAM设计的.
InnoDB中的row_search_for_mysql()/row_unlock_for_mysql()/row_insert_for_mysql()/row_update_for_mysql()等核心API,如名字所示,这些函数有通用的含义:使用MySQL中SQL层格式来表示一个记录,并做合适的格式转换后,做相应的处理,查询、更新、插入操作,这些相应的转换格式函数实现在storage/innobase/row/row0mysql.c
ha_innobase与格式转换相关的核心成员是innobase_perbuilt,该结构体另藏有row_prebuilt_struct*指针,
该指针指向一个按照记录来有效组织InnoDB表数据的结构体。
事务日志细节、行或页锁、实现事务隔离级别、提交与回滚、以及其他事务关键属性基本都是在存储引擎级别实现,所以,存储引擎跟SQL层交互需要使用统一的接口。
存储引擎事务实现综述
集成第三方存储引擎,需要定义两部分:定义与实现handler子类以及handlerton实现
不可避免,事务的实现一定需要实现handler子类的大量虚拟函数,但是事务实现的核心工作实在handlerton函数里面实现。可以这么理解:handler子类从概念上面与特定的表实例,而handlerton函数集却是与线程/连接相关。所以如COMMIT、ROLLBACK、SAVEPOINT等操作适合集成到handlerton。
事务本质实现方式完全取决于存储引擎。
由于与存储引擎交互是统一的接口协议,核心SQL不会了解到底层存储引擎的不同。
集成事务型存储引擎,存在很多因素需要考虑:
1、与非事务型甚至其他存储引擎类型的表交互问题
2、与查询缓存交互
3、与binlog交互
4、如何避免死锁
InnoDB提供了很好的范例,见sql/ha_innodb.cc文件,会注意到如何解决这些集成交互问题的。
>>实现handler子类
1、handler::has_transactions(). 告诉SQL层是否支持事务。
2、handler::start_stmt()/handler::external_lock()
两者用来让事务型存储引擎启动一个事务
external_lock() 在表实例被初始化都会被调用来开始事务处理,这与之前设计用来阻止表被外部应用修改的用途已经相去甚远。
但如果 使用LOCK TABLES人为锁定表时,handler::start_stmt()会被调用。
3、事务型存储引擎需要维护数据结构来跟踪当前事务的状态。THD结构中存在一个事务结构指针,指向ha_data数组。每个存储引擎在这个数组中都存在一个固定位移值,该位移值自动生成,并记录在handlerton的slot成员变量中。该变量在事务开始的时候被初始化,如InnoDB:
thd->ha_data[innobase_hton.slot] = trx;
4、事务开始后,存储引擎需要通过调用tran_register_ha()在核心SQL层注册。以便给SQL层留通知入口。InnoDB中,注册动作在两个函数里面实现:innobase_register_stmt()和innobase_register_trx_and_stmt().
innobase_register_stmt()调用trans_register_ha(thd,FALSE,&innobase_hton);
innobase_register_trx_and_stmt()调用trans_register_ha(thd,TRUE,&innobase_hton);
第二个参数设置为false,表示只是注册事务的当前语句;如果设置为true,表示注册整个事务.
注册的目的是为了协助COMMIT和ROLLBACK操作,当处理这些操作时,核心server代码需要定位到handlerton以便调用特定的存储引擎来通知底层.
5、启动事务两种方式:内部隐式启动和外部显式启动。
外部显式启动:当client调用BEGIN或者START TRANSACTION.
内部隐式启动:发起一个查询,事务型存储引擎内部默认启动事务.
当外部显式启动事务时,SQL层能够控制和记录存储引擎的当前运行状态。
而内部自动启动事务,SQL层没有控制机会,所以需要通过事务注册流程来通知上面SQL层。
6、handler::try_semi_consistent_read(), handler::was_semi_consistent_read()和handler::unlock_row()用于避免在update和delete请求中发生锁等待。
InnoDB从handler类继承,定义在sql/ha_innodb.cc。
需要大量的工作来转换原生InnoDB数据结构与MySQL上层数据格式的差异,因为上层数据格式设计大多数原来是为MyISAM设计的.
InnoDB中的row_search_for_mysql()/row_unlock_for_mysql()/row_insert_for_mysql()/row_update_for_mysql()等核心API,如名字所示,这些函数有通用的含义:使用MySQL中SQL层格式来表示一个记录,并做合适的格式转换后,做相应的处理,查询、更新、插入操作,这些相应的转换格式函数实现在storage/innobase/row/row0mysql.c
ha_innobase与格式转换相关的核心成员是innobase_perbuilt,该结构体另藏有row_prebuilt_struct*指针,
该指针指向一个按照记录来有效组织InnoDB表数据的结构体。
row_prebuilt_struct在ha_innodb::open()中通过调用row_create_prebuilt()初始化。
row_prebuilt_struct定义在storage/innobase/include/row0mysql.h,而
row_create_prebuilt()在storage/innobase/row/row0mysql.c定义。
对该结构体的研究,对InnoDB的内在实现原理的学习很有帮助。
更细节化相关的结构体trx_struct,对应成员变量trx.
trx_struct结构体指向事务信息,包含了事务ID,事务隔离级别,事务是否创建删除表或索引,事务最后提交相关的log序列号,事务的日志名字,binlog中的位移,以及一系列标志位。InnoDB将该变量保存在thd->ha_data[innobase_hton.slot].
trx_struct定义在storage/innobase/include/trx0trx.h,通过trx_create()(storage/innobase/trx/trx0trx.c)初始化。InnoDB使用trx_allocate_for_mysql()的封装,而不是直接调用trx_create().
rnd_next(),index_first(),index_next(),index_prev()函数往往通过ha_innobase::general_fetch()实现,转而调用不同的InnoDB API,后续具体函数定义在storage/innobase/row/row0mysql.c
>>定义handlerton
使用单例模式确保存储引擎类别仅有一个对象handlerton,与handler类不同,handlerton不与表实例相关联。
row_prebuilt_struct定义在storage/innobase/include/row0mysql.h,而
row_create_prebuilt()在storage/innobase/row/row0mysql.c定义。
对该结构体的研究,对InnoDB的内在实现原理的学习很有帮助。
更细节化相关的结构体trx_struct,对应成员变量trx.
trx_struct结构体指向事务信息,包含了事务ID,事务隔离级别,事务是否创建删除表或索引,事务最后提交相关的log序列号,事务的日志名字,binlog中的位移,以及一系列标志位。InnoDB将该变量保存在thd->ha_data[innobase_hton.slot].
trx_struct定义在storage/innobase/include/trx0trx.h,通过trx_create()(storage/innobase/trx/trx0trx.c)初始化。InnoDB使用trx_allocate_for_mysql()的封装,而不是直接调用trx_create().
rnd_next(),index_first(),index_next(),index_prev()函数往往通过ha_innobase::general_fetch()实现,转而调用不同的InnoDB API,后续具体函数定义在storage/innobase/row/row0mysql.c
>>定义handlerton
使用单例模式确保存储引擎类别仅有一个对象handlerton,与handler类不同,handlerton不与表实例相关联。
handlerton定义一系列存储引擎的对应操作接口,可以从定义中发现大量函数都是事务相关的操作。
InnoDB handlerton定义在sql/ha_innodb.cc
与查询缓存交互
通知缓存表数据是否被修改,对于非事务型存储引擎来说简单的事情,但对于支持多种隔离级别的事务型存储引擎并非如此。
handler::register_query_cache_table()提供了让存储引擎回答对于查询进行缓存是否安全的机会。如果该函数没有实现,则默认在每一个commit都会让事务相关的表关联的所有查询缓存失效。
InnoDB使用innobase_query_caching_of_table_permitted()作为回调函数,通过复杂的分析来决定是否缓存。
与binlog交互
事务型存储引擎需要确保Binlog内容与数据库的状态保持一致。
SQL语句直到事务提交才会写入Binlog,事务回滚的话不会写入任何binlog日志。虽然如此,依然存在几个关键问题需要处理:
1、奔溃时需要确保binlog和表数据的一致,需要支持XA事务
2、语句复制模式下,从库会在一个线程内重放更新操作,所以主库确保序列化事务隔离级别来保证从库同样的结果。
避免死锁
InnoDB死锁检测算法。
加锁检测、超时检测(变量innodb_lock_wait_timeout)、交叉存储引擎死锁
InnoDB handlerton定义在sql/ha_innodb.cc
与查询缓存交互
通知缓存表数据是否被修改,对于非事务型存储引擎来说简单的事情,但对于支持多种隔离级别的事务型存储引擎并非如此。
handler::register_query_cache_table()提供了让存储引擎回答对于查询进行缓存是否安全的机会。如果该函数没有实现,则默认在每一个commit都会让事务相关的表关联的所有查询缓存失效。
InnoDB使用innobase_query_caching_of_table_permitted()作为回调函数,通过复杂的分析来决定是否缓存。
与binlog交互
事务型存储引擎需要确保Binlog内容与数据库的状态保持一致。
SQL语句直到事务提交才会写入Binlog,事务回滚的话不会写入任何binlog日志。虽然如此,依然存在几个关键问题需要处理:
1、奔溃时需要确保binlog和表数据的一致,需要支持XA事务
2、语句复制模式下,从库会在一个线程内重放更新操作,所以主库确保序列化事务隔离级别来保证从库同样的结果。
避免死锁
InnoDB死锁检测算法。
加锁检测、超时检测(变量innodb_lock_wait_timeout)、交叉存储引擎死锁