github.com/XiaoMi/Gaea@v1.2.5/proxy/server/executor.go (about)

     1  // Copyright 2019 The Gaea Authors. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package server
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/XiaoMi/Gaea/backend"
    27  	"github.com/XiaoMi/Gaea/core/errors"
    28  	"github.com/XiaoMi/Gaea/log"
    29  	"github.com/XiaoMi/Gaea/mysql"
    30  	"github.com/XiaoMi/Gaea/parser"
    31  	"github.com/XiaoMi/Gaea/parser/ast"
    32  	"github.com/XiaoMi/Gaea/parser/format"
    33  	"github.com/XiaoMi/Gaea/proxy/plan"
    34  	"github.com/XiaoMi/Gaea/util"
    35  	"github.com/XiaoMi/Gaea/util/hack"
    36  )
    37  
    38  const (
    39  	// master comments
    40  	masterComment = "/*master*/"
    41  	// general query log variable
    42  	gaeaGeneralLogVariable = "gaea_general_log"
    43  )
    44  
    45  // SessionExecutor is bound to a session, so requests are serializable
    46  type SessionExecutor struct {
    47  	manager *Manager
    48  
    49  	namespace  string
    50  	user       string
    51  	db         string
    52  	clientAddr string
    53  
    54  	status       uint16
    55  	lastInsertID uint64
    56  
    57  	collation        mysql.CollationID
    58  	charset          string
    59  	sessionVariables *mysql.SessionVariables
    60  
    61  	txConns    map[string]backend.PooledConnect
    62  	savepoints []string
    63  	txLock     sync.Mutex
    64  
    65  	stmtID uint32
    66  	stmts  map[uint32]*Stmt //prepare相关,client端到proxy的stmt
    67  
    68  	parser *parser.Parser
    69  }
    70  
    71  // Response response info
    72  type Response struct {
    73  	RespType int
    74  	Status   uint16
    75  	Data     interface{}
    76  }
    77  
    78  const (
    79  	// RespOK means OK message
    80  	RespOK = iota
    81  	// RespResult means Result message
    82  	RespResult
    83  	// RespError means error message
    84  	RespError
    85  	// RespFieldList means field list message
    86  	RespFieldList
    87  	// RespPrepare prepare response message
    88  	RespPrepare
    89  	// RespEOF means EOF message
    90  	RespEOF
    91  	// RespNoop means empty message
    92  	RespNoop
    93  )
    94  
    95  // CreateOKResponse create ok response
    96  func CreateOKResponse(status uint16) Response {
    97  	return Response{
    98  		RespType: RespOK,
    99  		Status:   status,
   100  	}
   101  }
   102  
   103  // CreateResultResponse create result response
   104  func CreateResultResponse(status uint16, result *mysql.Result) Response {
   105  	return Response{
   106  		RespType: RespResult,
   107  		Status:   status,
   108  		Data:     result,
   109  	}
   110  }
   111  
   112  // CreateErrorResponse create error response
   113  func CreateErrorResponse(status uint16, err error) Response {
   114  	return Response{
   115  		RespType: RespError,
   116  		Status:   status,
   117  		Data:     err,
   118  	}
   119  }
   120  
   121  // CreateFieldListResponse create field list response
   122  func CreateFieldListResponse(status uint16, fl []*mysql.Field) Response {
   123  	return Response{
   124  		RespType: RespFieldList,
   125  		Status:   status,
   126  		Data:     fl,
   127  	}
   128  }
   129  
   130  // CreatePrepareResponse create prepare response
   131  func CreatePrepareResponse(status uint16, stmt *Stmt) Response {
   132  	return Response{
   133  		RespType: RespPrepare,
   134  		Status:   status,
   135  		Data:     stmt,
   136  	}
   137  }
   138  
   139  // CreateEOFResponse create eof response
   140  func CreateEOFResponse(status uint16) Response {
   141  	return Response{
   142  		RespType: RespEOF,
   143  		Status:   status,
   144  	}
   145  }
   146  
   147  // CreateNoopResponse no op response, for ComStmtClose
   148  func CreateNoopResponse() Response {
   149  	return Response{
   150  		RespType: RespNoop,
   151  	}
   152  }
   153  
   154  func newSessionExecutor(manager *Manager) *SessionExecutor {
   155  
   156  	return &SessionExecutor{
   157  		sessionVariables: mysql.NewSessionVariables(),
   158  		txConns:          make(map[string]backend.PooledConnect),
   159  		stmts:            make(map[uint32]*Stmt),
   160  		parser:           parser.New(),
   161  		status:           initClientConnStatus,
   162  		manager:          manager,
   163  	}
   164  }
   165  
   166  // GetNamespace return namespace in session
   167  func (se *SessionExecutor) GetNamespace() *Namespace {
   168  	return se.manager.GetNamespace(se.namespace)
   169  }
   170  
   171  // GetVariables return variables in session
   172  func (se *SessionExecutor) GetVariables() *mysql.SessionVariables {
   173  	return se.sessionVariables
   174  }
   175  
   176  func (se *SessionExecutor) setIntSessionVariable(name string, valueStr string) error {
   177  	if strings.ToLower(valueStr) == mysql.KeywordDefault {
   178  		se.sessionVariables.Delete(name)
   179  		return nil
   180  	}
   181  
   182  	value, err := strconv.ParseInt(valueStr, 10, 64)
   183  	if err != nil {
   184  		return err
   185  	}
   186  	if err = se.sessionVariables.Set(name, value); err != nil {
   187  		return err
   188  	}
   189  	return nil
   190  }
   191  
   192  func (se *SessionExecutor) setStringSessionVariable(name string, valueStr string) error {
   193  	if strings.ToLower(valueStr) == mysql.KeywordDefault {
   194  		se.sessionVariables.Delete(name)
   195  		return nil
   196  	}
   197  
   198  	return se.sessionVariables.Set(name, valueStr)
   199  }
   200  
   201  func (se *SessionExecutor) setGeneralLogVariable(valueStr string) error {
   202  	v, err := strconv.Atoi(valueStr)
   203  	if err != nil {
   204  		return errors.ErrInvalidArgument
   205  	}
   206  	atomic.StoreUint32(&ProcessGeneralLog, uint32(v))
   207  	return nil
   208  }
   209  
   210  // GetLastInsertID return last_inert_id
   211  func (se *SessionExecutor) GetLastInsertID() uint64 {
   212  	return se.lastInsertID
   213  }
   214  
   215  // SetLastInsertID store last_insert_id
   216  func (se *SessionExecutor) SetLastInsertID(id uint64) {
   217  	se.lastInsertID = id
   218  }
   219  
   220  // GetStatus return session status
   221  func (se *SessionExecutor) GetStatus() uint16 {
   222  	return se.status
   223  }
   224  
   225  // SetStatus store status
   226  func (se *SessionExecutor) SetStatus(status uint16) {
   227  	se.status = status
   228  }
   229  
   230  // SetCollationID store collation id
   231  func (se *SessionExecutor) SetCollationID(id mysql.CollationID) {
   232  	se.collation = id
   233  }
   234  
   235  // SetNamespaceDefaultCollationID store default collation id
   236  func (se *SessionExecutor) SetNamespaceDefaultCollationID() {
   237  	se.collation = se.manager.GetNamespace(se.namespace).GetDefaultCollationID()
   238  }
   239  
   240  // GetCollationID return collation id
   241  func (se *SessionExecutor) GetCollationID() mysql.CollationID {
   242  	return se.collation
   243  }
   244  
   245  // SetCharset set session charset
   246  func (se *SessionExecutor) SetCharset(charset string) {
   247  	se.charset = charset
   248  }
   249  
   250  // SetNamespaceDefaultCharset set session default charset
   251  func (se SessionExecutor) SetNamespaceDefaultCharset() {
   252  	se.charset = se.manager.GetNamespace(se.namespace).GetDefaultCharset()
   253  }
   254  
   255  // GetCharset return charset
   256  func (se *SessionExecutor) GetCharset() string {
   257  	return se.charset
   258  }
   259  
   260  // SetDatabase set session database
   261  func (se *SessionExecutor) SetDatabase(db string) {
   262  	se.db = db
   263  }
   264  
   265  // GetDatabase return database in session
   266  func (se *SessionExecutor) GetDatabase() string {
   267  	return se.db
   268  }
   269  
   270  // ExecuteCommand execute command
   271  func (se *SessionExecutor) ExecuteCommand(cmd byte, data []byte) Response {
   272  	switch cmd {
   273  	case mysql.ComQuit:
   274  		se.handleRollback(nil)
   275  		// https://dev.mysql.com/doc/internals/en/com-quit.html
   276  		// either a connection close or a OK_Packet, OK_Packet will cause client RST sometimes, but doesn't affect sql execute
   277  		return CreateNoopResponse()
   278  	case mysql.ComQuery: // data type: string[EOF]
   279  		sql := string(data)
   280  		// handle phase
   281  		r, err := se.handleQuery(sql)
   282  		if err != nil {
   283  			return CreateErrorResponse(se.status, err)
   284  		}
   285  		return CreateResultResponse(se.status, r)
   286  	case mysql.ComPing:
   287  		return CreateOKResponse(se.status)
   288  	case mysql.ComInitDB:
   289  		db := string(data)
   290  		// handle phase
   291  		err := se.handleUseDB(db)
   292  		if err != nil {
   293  			return CreateErrorResponse(se.status, err)
   294  		}
   295  		return CreateOKResponse(se.status)
   296  	case mysql.ComFieldList:
   297  		fs, err := se.handleFieldList(data)
   298  		if err != nil {
   299  			return CreateErrorResponse(se.status, err)
   300  		}
   301  		return CreateFieldListResponse(se.status, fs)
   302  	case mysql.ComStmtPrepare:
   303  		sql := string(data)
   304  		stmt, err := se.handleStmtPrepare(sql)
   305  		if err != nil {
   306  			return CreateErrorResponse(se.status, err)
   307  		}
   308  		return CreatePrepareResponse(se.status, stmt)
   309  	case mysql.ComStmtExecute:
   310  		values := make([]byte, len(data))
   311  		copy(values, data)
   312  		r, err := se.handleStmtExecute(values)
   313  		if err != nil {
   314  			return CreateErrorResponse(se.status, err)
   315  		}
   316  		return CreateResultResponse(se.status, r)
   317  	case mysql.ComStmtClose: // no response
   318  		if err := se.handleStmtClose(data); err != nil {
   319  			return CreateErrorResponse(se.status, err)
   320  		}
   321  		return CreateNoopResponse()
   322  	case mysql.ComStmtSendLongData: // no response
   323  		values := make([]byte, len(data))
   324  		copy(values, data)
   325  		if err := se.handleStmtSendLongData(values); err != nil {
   326  			return CreateErrorResponse(se.status, err)
   327  		}
   328  		return CreateNoopResponse()
   329  	case mysql.ComStmtReset:
   330  		if err := se.handleStmtReset(data); err != nil {
   331  			return CreateErrorResponse(se.status, err)
   332  		}
   333  		return CreateOKResponse(se.status)
   334  	case mysql.ComSetOption:
   335  		return CreateEOFResponse(se.status)
   336  	default:
   337  		msg := fmt.Sprintf("command %d not supported now", cmd)
   338  		log.Warn("dispatch command failed, error: %s", msg)
   339  		return CreateErrorResponse(se.status, mysql.NewError(mysql.ErrUnknown, msg))
   340  	}
   341  }
   342  
   343  func (se *SessionExecutor) getBackendConns(sqls map[string]map[string][]string, fromSlave bool) (pcs map[string]backend.PooledConnect, err error) {
   344  	pcs = make(map[string]backend.PooledConnect)
   345  	for sliceName := range sqls {
   346  		var pc backend.PooledConnect
   347  		pc, err = se.getBackendConn(sliceName, fromSlave)
   348  		if err != nil {
   349  			return
   350  		}
   351  		pcs[sliceName] = pc
   352  	}
   353  	return
   354  }
   355  
   356  func (se *SessionExecutor) getBackendConn(sliceName string, fromSlave bool) (pc backend.PooledConnect, err error) {
   357  	if !se.isInTransaction() {
   358  		slice := se.GetNamespace().GetSlice(sliceName)
   359  		return slice.GetConn(fromSlave, se.GetNamespace().GetUserProperty(se.user))
   360  	}
   361  	return se.getTransactionConn(sliceName)
   362  }
   363  
   364  func (se *SessionExecutor) getTransactionConn(sliceName string) (pc backend.PooledConnect, err error) {
   365  	se.txLock.Lock()
   366  	defer se.txLock.Unlock()
   367  
   368  	var ok bool
   369  	pc, ok = se.txConns[sliceName]
   370  
   371  	if !ok {
   372  		slice := se.GetNamespace().GetSlice(sliceName) // returns nil only when the conf is error (fatal) so panic is correct
   373  		if pc, err = slice.GetMasterConn(); err != nil {
   374  			return
   375  		}
   376  
   377  		if !se.isAutoCommit() {
   378  			if err = pc.SetAutoCommit(0); err != nil {
   379  				pc.Close()
   380  				pc.Recycle()
   381  				return
   382  			}
   383  		} else {
   384  			if err = pc.Begin(); err != nil {
   385  				pc.Close()
   386  				pc.Recycle()
   387  				return
   388  			}
   389  		}
   390  		for _, savepoint := range se.savepoints {
   391  			pc.Execute("savepoint "+savepoint, 0)
   392  		}
   393  		se.txConns[sliceName] = pc
   394  	}
   395  
   396  	return
   397  }
   398  
   399  func (se *SessionExecutor) recycleBackendConn(pc backend.PooledConnect, rollback bool) {
   400  	if pc == nil {
   401  		return
   402  	}
   403  
   404  	if se.isInTransaction() {
   405  		return
   406  	}
   407  
   408  	if rollback {
   409  		pc.Rollback()
   410  	}
   411  
   412  	pc.Recycle()
   413  }
   414  
   415  func (se *SessionExecutor) recycleBackendConns(pcs map[string]backend.PooledConnect, rollback bool) {
   416  	if se.isInTransaction() {
   417  		return
   418  	}
   419  
   420  	for _, pc := range pcs {
   421  		if pc == nil {
   422  			continue
   423  		}
   424  		if rollback {
   425  			pc.Rollback()
   426  		}
   427  		pc.Recycle()
   428  	}
   429  }
   430  
   431  func initBackendConn(pc backend.PooledConnect, phyDB string, charset string, collation mysql.CollationID, sessionVariables *mysql.SessionVariables) error {
   432  	if err := pc.UseDB(phyDB); err != nil {
   433  		return err
   434  	}
   435  
   436  	charsetChanged, err := pc.SetCharset(charset, collation)
   437  	if err != nil {
   438  		return err
   439  	}
   440  
   441  	variablesChanged, err := pc.SetSessionVariables(sessionVariables)
   442  	if err != nil {
   443  		return err
   444  	}
   445  
   446  	if charsetChanged || variablesChanged {
   447  		if err = pc.WriteSetStatement(); err != nil {
   448  			return err
   449  		}
   450  	}
   451  
   452  	return nil
   453  }
   454  
   455  func (se *SessionExecutor) executeInMultiSlices(reqCtx *util.RequestContext, pcs map[string]backend.PooledConnect,
   456  	sqls map[string]map[string][]string) ([]*mysql.Result, error) {
   457  
   458  	parallel := len(pcs)
   459  	if parallel != len(sqls) {
   460  		log.Warn("Session executeInMultiSlices error, conns: %v, sqls: %v, error: %s", pcs, sqls, errors.ErrConnNotEqual.Error())
   461  		return nil, errors.ErrConnNotEqual
   462  	} else if parallel == 0 {
   463  		return nil, errors.ErrNoPlan
   464  	}
   465  
   466  	var ctx = context.Background()
   467  	var cancel context.CancelFunc
   468  	maxExecuteTime := se.manager.GetNamespace(se.namespace).GetMaxExecuteTime()
   469  	if maxExecuteTime > 0 {
   470  		ctx, cancel = context.WithTimeout(context.Background(), time.Duration(maxExecuteTime)*time.Millisecond)
   471  		defer cancel()
   472  	}
   473  
   474  	// Control go routine execution
   475  	done := make(chan string, parallel)
   476  	defer close(done)
   477  
   478  	// This map is not thread safe.
   479  	pcsUnCompleted := make(map[string]backend.PooledConnect, parallel)
   480  	for sliceName, pc := range pcs {
   481  		pcsUnCompleted[sliceName] = pc
   482  	}
   483  
   484  	resultCount := 0
   485  	for _, sqlSlice := range sqls {
   486  		for _, sqlDB := range sqlSlice {
   487  			resultCount += len(sqlDB)
   488  		}
   489  	}
   490  	rs := make([]interface{}, resultCount)
   491  	f := func(reqCtx *util.RequestContext, rs []interface{}, i int, sliceName string, execSqls map[string][]string, pc backend.PooledConnect) {
   492  		for db, sqls := range execSqls {
   493  			err := initBackendConn(pc, db, se.GetCharset(), se.GetCollationID(), se.GetVariables())
   494  			if err != nil {
   495  				rs[i] = err
   496  				break
   497  			}
   498  			for _, v := range sqls {
   499  				startTime := time.Now()
   500  				r, err := pc.Execute(v, se.manager.GetNamespace(se.namespace).GetMaxResultSize())
   501  				se.manager.RecordBackendSQLMetrics(reqCtx, se.namespace, v, pc.GetAddr(), startTime, err)
   502  				if err != nil {
   503  					rs[i] = err
   504  				} else {
   505  					rs[i] = r
   506  				}
   507  				i++
   508  			}
   509  		}
   510  		done <- sliceName
   511  	}
   512  
   513  	offset := 0
   514  	for sliceName, pc := range pcs {
   515  		s := sqls[sliceName] //map[string][]string
   516  		go f(reqCtx, rs, offset, sliceName, s, pc)
   517  		for _, sqlDB := range sqls[sliceName] {
   518  			offset += len(sqlDB)
   519  		}
   520  	}
   521  
   522  	for i := 0; i < parallel; i++ {
   523  		select {
   524  		case sliceName := <-done:
   525  			delete(pcsUnCompleted, sliceName)
   526  		case <-ctx.Done():
   527  			for sliceName, pc := range pcsUnCompleted {
   528  				connID := pc.GetConnectionID()
   529  				dc, err := se.manager.GetNamespace(se.namespace).GetSlice(sliceName).GetDirectConn(pc.GetAddr())
   530  				if err != nil {
   531  					log.Warn("kill thread id: %d failed, get connection err: %v", connID, err.Error())
   532  					continue
   533  				}
   534  				if _, err = dc.Execute(fmt.Sprintf("KILL QUERY %d", connID), 0); err != nil {
   535  					log.Warn("kill thread id: %d failed, err: %v", connID, err.Error())
   536  				}
   537  				dc.Close()
   538  			}
   539  			for j := 0; j < len(pcsUnCompleted); j++ {
   540  				<-done
   541  			}
   542  			return nil, fmt.Errorf("%v %dms", errors.ErrTimeLimitExceeded, maxExecuteTime)
   543  		}
   544  	}
   545  
   546  	var err error
   547  	r := make([]*mysql.Result, resultCount)
   548  	for i, v := range rs {
   549  		if e, ok := v.(error); ok {
   550  			err = e
   551  			break
   552  		}
   553  		if rs[i] != nil {
   554  			r[i] = rs[i].(*mysql.Result)
   555  		}
   556  	}
   557  	return r, err
   558  }
   559  
   560  func canHandleWithoutPlan(stmtType int) bool {
   561  	return stmtType == parser.StmtShow ||
   562  		stmtType == parser.StmtSet ||
   563  		stmtType == parser.StmtBegin ||
   564  		stmtType == parser.StmtCommit ||
   565  		stmtType == parser.StmtRollback ||
   566  		stmtType == parser.StmtSavepoint ||
   567  		stmtType == parser.StmtUse
   568  }
   569  
   570  const variableRestoreFlag = format.RestoreKeyWordLowercase | format.RestoreNameLowercase
   571  
   572  // 获取SET语句中变量的字符串值, 去掉各种引号并转换为小写
   573  func getVariableExprResult(v ast.ExprNode) string {
   574  	s := &strings.Builder{}
   575  	ctx := format.NewRestoreCtx(variableRestoreFlag, s)
   576  	v.Restore(ctx)
   577  	return strings.ToLower(s.String())
   578  }
   579  
   580  func getOnOffVariable(v string) (string, error) {
   581  	if v == "1" || v == "on" {
   582  		return "1", nil
   583  	} else if v == "0" || v == "off" {
   584  		return "0", nil
   585  	} else {
   586  		return "", fmt.Errorf("not an on off string")
   587  	}
   588  }
   589  
   590  // master-slave routing
   591  func canExecuteFromSlave(c *SessionExecutor, sql string) bool {
   592  	if parser.Preview(sql) != parser.StmtSelect {
   593  		return false
   594  	}
   595  
   596  	_, comments := parser.SplitMarginComments(sql)
   597  	lcomment := strings.ToLower(strings.TrimSpace(comments.Leading))
   598  	var fromSlave = c.GetNamespace().IsRWSplit(c.user)
   599  	if strings.ToLower(lcomment) == masterComment {
   600  		fromSlave = false
   601  	}
   602  
   603  	return fromSlave
   604  }
   605  
   606  // 如果是只读用户, 且SQL是INSERT, UPDATE, DELETE, 则拒绝执行, 返回true
   607  func isSQLNotAllowedByUser(c *SessionExecutor, stmtType int) bool {
   608  	if c.GetNamespace().IsAllowWrite(c.user) {
   609  		return false
   610  	}
   611  
   612  	return stmtType == parser.StmtDelete || stmtType == parser.StmtInsert || stmtType == parser.StmtUpdate
   613  }
   614  
   615  func modifyResultStatus(r *mysql.Result, cc *SessionExecutor) {
   616  	r.Status = r.Status | cc.GetStatus()
   617  }
   618  
   619  func createShowDatabaseResult(dbs []string) *mysql.Result {
   620  	r := new(mysql.Resultset)
   621  
   622  	field := &mysql.Field{}
   623  	field.Name = hack.Slice("Database")
   624  	r.Fields = append(r.Fields, field)
   625  
   626  	for _, db := range dbs {
   627  		r.Values = append(r.Values, []interface{}{db})
   628  	}
   629  
   630  	result := &mysql.Result{
   631  		AffectedRows: uint64(len(dbs)),
   632  		Resultset:    r,
   633  	}
   634  
   635  	plan.GenerateSelectResultRowData(result)
   636  	return result
   637  }
   638  
   639  func createShowGeneralLogResult() *mysql.Result {
   640  	r := new(mysql.Resultset)
   641  
   642  	field := &mysql.Field{}
   643  	field.Name = hack.Slice(gaeaGeneralLogVariable)
   644  	r.Fields = append(r.Fields, field)
   645  
   646  	var value string
   647  	if OpenProcessGeneralQueryLog() {
   648  		value = "ON"
   649  	} else {
   650  		value = "OFF"
   651  	}
   652  	r.Values = append(r.Values, []interface{}{value})
   653  	result := &mysql.Result{
   654  		AffectedRows: 1,
   655  		Resultset:    r,
   656  	}
   657  
   658  	plan.GenerateSelectResultRowData(result)
   659  	return result
   660  }
   661  
   662  func getFromSlave(reqCtx *util.RequestContext) bool {
   663  	slaveFlag := reqCtx.Get(util.FromSlave)
   664  	if slaveFlag != nil && slaveFlag.(int) == 1 {
   665  		return true
   666  	}
   667  
   668  	return false
   669  }
   670  
   671  func (se *SessionExecutor) isInTransaction() bool {
   672  	return se.status&mysql.ServerStatusInTrans > 0 ||
   673  		!se.isAutoCommit()
   674  }
   675  
   676  func (se *SessionExecutor) isAutoCommit() bool {
   677  	return se.status&mysql.ServerStatusAutocommit > 0
   678  }
   679  
   680  func (se *SessionExecutor) handleBegin() error {
   681  	se.txLock.Lock()
   682  	defer se.txLock.Unlock()
   683  
   684  	for _, co := range se.txConns {
   685  		if err := co.Begin(); err != nil {
   686  			return err
   687  		}
   688  	}
   689  	se.status |= mysql.ServerStatusInTrans
   690  	se.savepoints = []string{}
   691  	return nil
   692  }
   693  
   694  func (se *SessionExecutor) handleCommit() (err error) {
   695  	if err := se.commit(); err != nil {
   696  		return err
   697  	}
   698  	return nil
   699  
   700  }
   701  
   702  func (se *SessionExecutor) handleRollback(stmt *ast.RollbackStmt) (err error) {
   703  	if stmt == nil || stmt.Savepoint == "" {
   704  		return se.rollback()
   705  	} else {
   706  		return se.rollbackSavepoint(stmt.Savepoint)
   707  	}
   708  }
   709  
   710  func (se *SessionExecutor) commit() (err error) {
   711  	se.txLock.Lock()
   712  	defer se.txLock.Unlock()
   713  
   714  	se.status &= ^mysql.ServerStatusInTrans
   715  
   716  	for _, pc := range se.txConns {
   717  		if e := pc.Commit(); e != nil {
   718  			err = e
   719  		}
   720  		pc.Recycle()
   721  	}
   722  
   723  	se.txConns = make(map[string]backend.PooledConnect)
   724  	se.savepoints = []string{}
   725  	return
   726  }
   727  
   728  func (se *SessionExecutor) rollback() (err error) {
   729  	se.txLock.Lock()
   730  	defer se.txLock.Unlock()
   731  	se.status &= ^mysql.ServerStatusInTrans
   732  	for _, pc := range se.txConns {
   733  		err = pc.Rollback()
   734  		pc.Recycle()
   735  	}
   736  	se.txConns = make(map[string]backend.PooledConnect)
   737  	se.savepoints = []string{}
   738  	return
   739  }
   740  
   741  func (se *SessionExecutor) rollbackSavepoint(savepoint string) (err error) {
   742  	se.txLock.Lock()
   743  	defer se.txLock.Unlock()
   744  	for _, pc := range se.txConns {
   745  		_, err = pc.Execute("rollback to "+savepoint, 0)
   746  	}
   747  	if err == nil && se.isInTransaction() {
   748  		if index := util.ArrayFindIndex(se.savepoints, savepoint); index > -1 {
   749  			se.savepoints = se.savepoints[0:index]
   750  		}
   751  	}
   752  	return
   753  }
   754  
   755  func (se *SessionExecutor) handleSavepoint(stmt *ast.SavepointStmt) (err error) {
   756  	se.txLock.Lock()
   757  	defer se.txLock.Unlock()
   758  	if stmt.Release {
   759  		for _, pc := range se.txConns {
   760  			_, err = pc.Execute("release savepoint "+stmt.Savepoint, 0)
   761  		}
   762  		if err == nil && se.isInTransaction() {
   763  			if index := util.ArrayFindIndex(se.savepoints, stmt.Savepoint); index > -1 {
   764  				se.savepoints = se.savepoints[0 : index+1]
   765  			}
   766  		}
   767  	} else {
   768  		for _, pc := range se.txConns {
   769  			_, err = pc.Execute("savepoint "+stmt.Savepoint, 0)
   770  		}
   771  		if err == nil && se.isInTransaction() {
   772  			if util.ArrayFindIndex(se.savepoints, stmt.Savepoint) > -1 {
   773  				se.savepoints = util.ArrayRemoveItem(se.savepoints, stmt.Savepoint)
   774  			}
   775  			se.savepoints = append(se.savepoints, stmt.Savepoint)
   776  		}
   777  	}
   778  	return
   779  }
   780  
   781  // ExecuteSQL execute sql
   782  func (se *SessionExecutor) ExecuteSQL(reqCtx *util.RequestContext, slice, db, sql string) (*mysql.Result, error) {
   783  	phyDB, err := se.GetNamespace().GetDefaultPhyDB(db)
   784  	if err != nil {
   785  		return nil, err
   786  	}
   787  
   788  	sqls := make(map[string]map[string][]string)
   789  	dbSQLs := make(map[string][]string)
   790  	dbSQLs[phyDB] = []string{sql}
   791  	sqls[slice] = dbSQLs
   792  
   793  	pcs, err := se.getBackendConns(sqls, getFromSlave(reqCtx))
   794  	defer se.recycleBackendConns(pcs, false)
   795  	if err != nil {
   796  		log.Warn("getUnShardConns failed: %v", err)
   797  		return nil, err
   798  	}
   799  
   800  	rs, err := se.executeInMultiSlices(reqCtx, pcs, sqls)
   801  	if err != nil {
   802  		return nil, err
   803  	}
   804  
   805  	if len(rs) == 0 {
   806  		return nil, mysql.NewError(mysql.ErrUnknown, "result is empty")
   807  	}
   808  	return rs[0], nil
   809  }
   810  
   811  // ExecuteSQLs len(sqls) must not be 0, or return error
   812  func (se *SessionExecutor) ExecuteSQLs(reqCtx *util.RequestContext, sqls map[string]map[string][]string) ([]*mysql.Result, error) {
   813  	if len(sqls) == 0 {
   814  		return nil, fmt.Errorf("no sql to execute")
   815  	}
   816  
   817  	pcs, err := se.getBackendConns(sqls, getFromSlave(reqCtx))
   818  	defer se.recycleBackendConns(pcs, false)
   819  	if err != nil {
   820  		log.Warn("getShardConns failed: %v", err)
   821  		return nil, err
   822  	}
   823  
   824  	rs, err := se.executeInMultiSlices(reqCtx, pcs, sqls)
   825  	if err != nil {
   826  		return nil, err
   827  	}
   828  	return rs, nil
   829  }