我的TIDB 4次PR的详解

https://github.com/pingcap/tidb/pull/3105

这是第一次PR,一个添加mysql内置函数的活动(活动详情),计算框架都已经设计好了,这里增加了一个MAKEDATE(),基本上就是跟着mysql官方文档的要求写的(https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_makedate)

这是一个入门级的活动,为了熟悉TIDB的开发模式。

https://github.com/pingcap/tidb/pull/3456

第二个PR,这个解决了一个我自己发现的BUG:

当运行这个SQL:

use test;
create table t (c varchar(30));
ALTER TABLE `test`.`t` ADD INDEX `index1` ();

会使得TIDB服务崩溃,并且重启后也一直闪退报错。

原因是ALTER TABLE `test`.`t` ADD INDEX `index1` ();这条不合法的DDL语句没有被语法解析器过滤掉,导致读取索引数组时越界了(数组大小0,访问了[0]元素),并且没有回收掉这个panic;然后由于TIDB的DDL异常退出后再下次重启服务时会继续执行,所以服务会无法正常启动了。

解决:在parser解析器里面过滤掉了这个错误的语法即可。

思考流程:

首先根据错误日志:

reorg.go:73: [info] [ddl] run reorg job done 
panic: runtime error: index out of range

goroutine 63 [running]:
github.com/pingcap/tidb/ddl.(*ddl).onCreateIndex(0xc4206c81c0, 0xc4206c1ca8, 0xc4200ca4d0, 0x1, 0x1, 0x0)
	/home/jenkins/workspace/build_tidb_master/go/src/github.com/pingcap/tidb/ddl/index.go:269 +0x78b
github.com/pingcap/tidb/ddl.(*ddl).runDDLJob(0xc4206c81c0, 0xc4206c1ca8, 0xc4200ca4d0, 0x0)
	/home/jenkins/workspace/build_tidb_master/go/src/github.com/pingcap/tidb/ddl/ddl_worker.go:298 +0x51a
github.com/pingcap/tidb/ddl.(*ddl).handleDDLJobQueue.func1(0x17675c0, 0xc420298a80, 0xc420298a80, 0x0)
	/home/jenkins/workspace/build_tidb_master/go/src/github.com/pingcap/tidb/ddl/ddl_worker.go:230 +0x31c
github.com/pingcap/tidb/kv.RunInNewTxn(0x175cae0, 0xc4206cc240, 0x455500, 0xc4206c1e48, 0xc420022df0, 0xc420022e30)
	/home/jenkins/workspace/build_tidb_master/go/src/github.com/pingcap/tidb/kv/txn.go:43 +0xce
github.com/pingcap/tidb/ddl.(*ddl).handleDDLJobQueue(0xc4206c81c0, 0xc420057500, 0x0)
	/home/jenkins/workspace/build_tidb_master/go/src/github.com/pingcap/tidb/ddl/ddl_worker.go:238 +0xff
github.com/pingcap/tidb/ddl.(*ddl).onDDLWorker(0xc4206c81c0)
	/home/jenkins/workspace/build_tidb_master/go/src/github.com/pingcap/tidb/ddl/ddl_worker.go:58 +0x21e
created by github.com/pingcap/tidb/ddl.(*ddl).start
	/home/jenkins/workspace/build_tidb_master/go/src/github.com/pingcap/tidb/ddl/ddl.go:323 +0xea

看到错误抛出在这里:

		addIndexColumnFlag(tblInfo, indexInfo)

		job.SchemaState = model.StatePublic
		ver, err := updateTableInfo(t, job, tblInfo, originalState)
		if err != nil {
			return ver, errors.Trace(err)
		}

		// Finish this job.
		job.State = model.JobDone
		job.BinlogInfo.AddTableInfo(ver, tblInfo)
	default:
		err = ErrInvalidIndexState.Gen("invalid index state %v", tblInfo.State)
	}

	return ver, errors.Trace(err)
}

https://github.com/pingcap/tidb/blob/72981fdbbd7e7d2c45b47f031d61fb4b3775dfb9/ddl/index.go#L269-L285

而进入addIndexColumnFlag:

func addIndexColumnFlag(tblInfo *model.TableInfo, indexInfo *model.IndexInfo) {
	col := indexInfo.Columns[0]

	if indexInfo.Unique && len(indexInfo.Columns) == 1 {
		tblInfo.Columns[col.Offset].Flag |= mysql.UniqueKeyFlag
	} else {
		tblInfo.Columns[col.Offset].Flag |= mysql.MultipleKeyFlag
	}
}

https://github.com/pingcap/tidb/blob/72981fdbbd7e7d2c45b47f031d61fb4b3775dfb9/ddl/index.go#L139-L147

发现这里的indexInfo.Columns长度为0,所以数组越界了。

一开始跟着SQL语句走DEBUG,走到了:

				v.err = checkDuplicateColumnName(spec.Constraint.Keys)
				if v.err != nil {
					return
				}
			default:
				// Nothing to do now.
			}
		case ast.AlterTableOption:
			for _, opt := range spec.Options {
				if opt.Tp == ast.TableOptionAutoIncrement {
					v.err = ErrAlterAutoID
					return
				}
			}
		default:
			// Nothing to do now.
		}
	}
}

https://github.com/pingcap/tidb/blob/7b381c0b00796199389e0ba991165a56a7632cb3/plan/validator.go#L267-L285

这里面是sql编译成语法树后检查语法树合法性的地方。

开始在这里调用了对索引是否为空的检查。

后来coocood 让我在语法解析器里面报错,为了和mysql报相同类型的错误,当时对于这个parser/parser.y 的语法没有接触过,就看了几个其他sql的语法限制,大致理解了语法,然后在:

|	KeyOrIndex IndexName IndexTypeOpt '(' IndexColNameList ')' IndexOptionList
	{
		c := &ast.Constraint{
			Tp:	ast.ConstraintIndex,
			Keys:	$5.([]*ast.IndexColName),
			Name:	$2.(string),
		}
		if $7 != nil {
			c.Option = $7.(*ast.IndexOption)
		}
		if $3 != nil {
			if c.Option == nil {
				c.Option = &ast.IndexOption{}
			}
			c.Option.Tp = $3.(model.IndexType)
		}
		$$ = c
	}
|	"UNIQUE" KeyOrIndexOpt IndexName IndexTypeOpt '(' IndexColNameList ')' IndexOptionList
	{
		c := &ast.Constraint{
			Tp:	ast.ConstraintUniq,
			Keys:	$6.([]*ast.IndexColName),
			Name:	$3.(string),
		}
		if $8 != nil {
			c.Option = $8.(*ast.IndexOption)
		}
		if $4 != nil {
			if c.Option == nil {
				c.Option = &ast.IndexOption{}
			}
			c.Option.Tp = $4.(model.IndexType)
		}
		$$ = c
	}

https://github.com/pingcap/tidb/blob/ddc2ffcfa55d8ad93f1b0017eee57edf96db2c63/parser/parser.y#L1383-L1418

找到了对索引检查语法的位置,于是在这里添加了:

        keys := $5.([]*ast.IndexColName)  
        if len(keys) < 1{  
            yylex.Errorf("IndexColNameList List can't be empty.")  
            return 1  
        }  

对长度的检查。

添加限制后的代码点击查看

然后coocood 提醒下发现:这两个限制都是调用了IndexColNameList:

IndexColNameList:
	{
		$$ = []*ast.IndexColName{}
	}
|	IndexColName
	{
		$$ = []*ast.IndexColName{$1.(*ast.IndexColName)}
	}
|	IndexColNameList ',' IndexColName
	{
		$$ = append($1.([]*ast.IndexColName), $3.(*ast.IndexColName))
	}

https://github.com/pingcap/tidb/blob/ddc2ffcfa55d8ad93f1b0017eee57edf96db2c63/parser/parser.y#L1570-L1581

只要移除

	{
		$$ = []*ast.IndexColName{}
	}

对空数组的允许即可。

https://github.com/pingcap/tidb/pull/3533

第三个PR。这个是PingCap最近发起的他们启用了新的计算框架后对所有内置函数的重写活动(详情)的当天提交的。

由于之前交过了MAKEDATE()的内置函数,所以这里用新的框架改写了。

https://github.com/pingcap/tidb/pull/3859

第四个PR。这个修复了一个别人提出的和MYSQL兼容性相关的问题:

在执行语句:

alter table t add column c varchar(4294967295)

时没有检查varchar的最大长度限制(mysql会根据当前表的字符集确定最大限制,TIDB目前还未支持在创建表的时候指定 库->表->字段这样的默认字符集获取,以及tidb支持的字符集有限,所以对已有字符集做了长度限制的支持)。

然后发现还有其他一些格式也需要限制长度(https://dev.mysql.com/doc/refman/5.7/en/storage-requirements.html),所以在这里统一了一个检查长度的方法,然后作为可扩展的标准,以方便以后加入新类型的限制。目前这个PR我已经加了char/varchar/float/set的限制。

发表评论

邮箱地址不会被公开。 必填项已用*标注

请输入正确的验证码