————————————-
当在set autocommit=0时,执行lock table write操作,如果此时有同一个表上进入Innodb层的DML,可能导致死锁,这种死锁MySQL不做检测,只能等待Innodb层超时,简单的分析如下:
1.
对于lock table write操作,backtrace如下:
SQL :set aucommit = 0 && lock tables t1 write:
mysql_execute_command
–>open_and_lock_tables_derived
–>simple_open_n_lock_tables
–>open_and_lock_tables_derived
–>lock_tables
–>mysql_lock_tables
在mysql_lock_tables函数中:
调用lock_external
->handler::ha_external_lock
->ha_innodb::external_lock
->row_lock_table_for_mysql 对innodb表进行加锁(LOCK_X)
随后调用thr_multi_lock
->thr_lock->wait_for_lock —>等待锁释放 (如果已经有DML进入Innodb层还没完成的话)
2.
对于进入Innodb层的DML,例如一条insert操作,该线程会被suspend:
row_insert_for_mysql
–>row_ins_step(thr)
–>lock_table(0, node->table, LOCK_IX, thr);
由于lock table操作为该表加了LOCK_X锁,因此这里insert操作在尝试加LOCK_IX锁时失败,返回
DB_LOCK_WAIT,线程被suspend,等待超时。
5.5通过MDL锁解决了MySQL层和Innodb层的死锁问题,但在5.1里,目前唯一的办法就是等待在innodb层超时。
.
.
.
.
.
.
.
.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
以下为本人自言自语,记录目的为以后调试方便,不保证正确性。。。。。。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
最近看到参数skip_external_locking,看了官方的解释,该参数看起来只对MyISAM引擎有用,用于在多进程(也就是有多个服务器程序时)对单个表操作时进行锁定,默认值为打开,也就是跳过外部锁定。
之前一直对external lock没啥概念,大概浏览了下,以下只是简略的笔记。
从代码里可以发现,innodb也实现了相应的external_lock函数。那么这个external lock是做什么的呢?
在handler.cc里对应的是:
handler::ha_external_lock(THD *thd, int lock_type)
调用的地方包括
sql_table.cc:
copy_data_between_tables
当alter table,需要拷贝数据时,对目标表上F_WRLCK锁,然后再向其中拷贝数据。
lock.cc:
mysql_lock_tables->lock_external->handler::ha_external_lock
mysql_unlock_tables->unlock_external->handler::ha_external_lock
mysql_lock_tables用于对SQL涉及到的表进行加锁,从lock_external里可以看到
当只对表做读操作时(TL_READ_NO_INSERT>=lock_type >= TL_READ),锁类型为F_RDLCK
否则为F_WRLCK
然后对涉及到的表依次调用(*tables)->file->ha_external_lock
opt_range.cc:
QUICK_RANGE_SELECT::init_ror_merged_scan
QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT
在初始化ROR(RowidOrderedRetrieval) merge scan时调用。
ha_partition.cc:
ha_partition::prepare_new_partition
ha_partition::cleanup_new_partition
在innodb层,调用的是ha_innobase::external_lock,主要做如下工作:
1.update_thd(thd);
2.设置prebuilt->sql_stat_start = TRUE
3.根据lock_type的类型值,设置相应的变量,所谓的external lock,并不是实际上加锁,仅仅是设置某些标记。加锁操作包括:
–当lock_type == F_WRLCK时:
prebuilt->select_lock_type = LOCK_X;
prebuilt->stored_select_lock_type = LOCK_X;
–当lock_type不等于F_UNLCK
(1).innobase_register_trx(ht, thd, trx);
(2).if (trx->isolation_level == TRX_ISO_SERIALIZABLE —-隔离级别为SERIALIZABLE
&& prebuilt->select_lock_type == LOCK_NONE
&& thd_test_options(
thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) {
prebuilt->select_lock_type = LOCK_S;
prebuilt->stored_select_lock_type = LOCK_S;
}
(3).对于LOCK TABLE操作,只有在AUTOCOMMIT = 0时,才会对Innodb表加锁(row_lock_table_for_mysql)
(4).trx->n_mysql_tables_in_use++;
prebuilt->mysql_has_locked = TRUE;
DBUG_RETURN(0);
—以下为解锁逻辑
(1)设置标记
trx->n_mysql_tables_in_use–;
prebuilt->mysql_has_locked = FALSE;
(2)innobase_release_stat_resources(trx);
if (trx->n_mysql_tables_in_use == 0) //表明该SQL语句已经完成,需要重置相关变量和标记
当设置为自动提交时,则会在innodb层提交事务(innobase_commit),否则如果隔离级别小于等于read commited,则关闭该SQL的read view。