开发者

How to Keep a MySQL Connection Open in Bash

I have a bash script that calls MySQL several times. Instead of having to reconnect to MySQL, is there a way to keep the connection 开发者_开发百科open? Ideally, the connection would close if the script exits early. I'm thinking named pipes would work but they would stay open.

Here's a quick pseudo-example of what I hope to find:


openMySQL
executeMySQL "SELECT 1"
exit 1
executeMySQL "SELECT 2"

I'm looking for the openMySQL and executeMySQL functions where the MySQL connection will actually close during the exit 1.


I have part of what I was looking for.

Keep the mysql connection open using fd=3 for writing:


exec 3> >(mysql)
echo "SELECT 1;" >&3
echo "SELECT 2;" >&3
exec 3>&-

Keep the mysql connection open using fd=3 for reading:


exec 3< <(echo "SELECT 1;SELECT 2;"|mysql|sed '1d')
while read <&3
do
  echo $REPLY
done

Note: sed '1d' removes the header.

Is there any way to merge these so you can write to one fd and read from another?


To the best of my understanding your question: coproc's available in zsh/ksh and also bash v4+ might be similar to what you have in mind, e.g.

bash4-4.1$ coproc MYSQL mysql -B -uroot 
[1] 10603
bash4-4.1$ jobs
[1]+  Running                 coproc COPROC MYSQL mysql -B -uroot &
bash4-4.1$ echo 'show databases;' | MYSQL
Database
information_schema
...

The command is kept running in the background, its stdin/stdout can accessed, it will finish (as a result its standard input closing/EOFing) as soon as the current shell exists...


I know this thread is old, but I was also looking for a comfortable bash mysql session implementation and didn't found something good enough for my needs, so I wrote my own one which I'd like to share with the world.

############### BASIC MYSQL SESSION IMPLEMENTATION FOR BASH (by Norman 

Geist 2015) #############
# requires coproc, stdbuf, mysql
#args: handle query
function mysql_check {
  local handle
  handle=(${1//_/ })
  #has right structure && is still running && we opened it?
  if [[ ${#handle[*]} == 3 ]] && ps -p ${handle[2]} 2>> /dev/null >> /dev/null && { echo "" >&${handle[1]}; } 2> /dev/null; then
    return 0
  fi
  return 1
}

# open mysql connection
#args: -u user [-H host] [-p passwd] -d db
#returns $HANDLE
function mysql_connect {
  local argv argc user pass host db HANDLEID i
  #prepare args
  argv=($*)
  argc=${#argv[*]}

  #get options
  user=""
  pass=""
  host="localhost"
  db=""
  for ((i=0; $i < $argc; i++))
  do
    if [[ ${argv[$i]} == "-h" ]]; then
      echo "Usage: -u user [-H host] [-p passwd] -d db"
      return 0
    elif [[ ${argv[$i]} == "-u" ]]; then
      i=$[$i+1]
      if [[ ${#argv[$i]} -gt 0 ]]; then
    user=${argv[$i]}
      else
    echo "ERROR: -u expects argument!"
    return 1
      fi
    elif [[ ${argv[$i]} == "-p" ]]; then
      i=$[$i+1]
      if [[ ${#argv[$i]} -gt 0 ]]; then
    pass="-p"${argv[$i]}
      else
    echo "ERROR: -p expects argument!"
    return 1
      fi
    elif [[ ${argv[$i]} == "-H" ]]; then
      i=$[$i+1]
      if [[ ${#argv[$i]} -gt 0 ]]; then
    host=${argv[$i]}
      else
    echo "ERROR: -H expects argument!"
    return 1
      fi
    elif [[ ${argv[$i]} == "-d" ]]; then
      i=$[$i+1]
      if [[ ${#argv[$i]} -gt 0 ]]; then
    db=${argv[$i]}
      else
    echo "ERROR: -d expects argument!"
    return 1
      fi
    fi
  done

  if [[ ${#user} -lt 1 || ${#db} -lt 1 ]]; then
    echo "ERROR: Options -u user and -d db are required!"
    return 1;
  fi

  #init connection and channels
  #we do it in XML cause otherwise we can't detect the end of data and so would need a read timeout O_o
  HANDLEID="MYSQL$RANDOM"
  eval "coproc $HANDLEID { stdbuf -oL mysql -u $user $pass -h $host -D $db --force --unbuffered --xml -vvv 2>&1; }" 2> /dev/null
  HANDLE=$(eval 'echo ${'${HANDLEID}'[0]}_${'${HANDLEID}'[1]}_${'${HANDLEID}'_PID}')
  if mysql_check $HANDLE; then
    export HANDLE
    return 0
  else
    echo "ERROR: Connection failed to $user@$host->DB:$db!"
    return 1
  fi
}

#args: handle query
#return: $DATA[0] = affected rows/number of sets; 
#        $DATA[1] = key=>values pairs following
#        $DATA[2]key; DATA[3]=val ...
function mysql_query {
  local handle query affected line results_open row_open cols key val 
  if ! mysql_check $1; then
    echo "ERROR: Connection not open!"
    return 1
  fi
  handle=(${1//_/ })

  #delimit query; otherwise we block forever/timeout
  query=$2
  if [[ ! "$query" =~ \;\$ ]]; then
    query="$query;"
  fi
  #send query
  echo "$query" >&${handle[1]}

  #get output
  DATA=();
  DATA[0]=0
  DATA[1]=0
  results_open=0
  row_open=0
  cols=0
  while read -t $MYSQL_READ_TIMEOUT -ru ${handle[0]} line
  do 
    #WAS ERROR?
    if [[ "$line" == *"ERROR"* ]]; then
      echo "$line"
      return 1
    #WAS INSERT/UPDATE?
    elif [[ "$line" == *"Query OK"* ]]; then
      affected=$([[ "$line" =~ Query\ OK\,\ ([0-9]+)\ rows?\ affected ]] && echo ${BASH_REMATCH[1]})
      DATA[0]=$affected
      export DATA
      return 0
    fi

    #BEGIN OF RESULTS
    if [[ $line =~ \<resultset ]]; then
      results_open=1
    fi

    #RESULTS
    if [[ $results_open == 1 ]]; then
      if [[ $line =~ \<row ]]; then
    row_open=1
    cols=0
      elif [[ $line =~ \<field && $row_open == 1 ]]; then
    key=$([[ "$line" =~ name\=\"([^\"]+)\" ]] && echo ${BASH_REMATCH[1]})
    val=$([[ "$line" =~ \>(.*)\<\/ ]] && echo ${BASH_REMATCH[1]} || echo "NULL")
    DATA[${#DATA[*]}]=$key
    DATA[${#DATA[*]}]=$val
    cols=$[$cols+1]
      elif [[ $line =~ \<\/row ]]; then
    row_open=0
    DATA[0]=$[${DATA[0]}+1]
    DATA[1]=$cols
      fi
    fi

    #END OF RESULTS
    if [[ $line =~ \<\/resultset ]]; then
      export DATA
      return 0
    fi
  done
  #we can only get here
  #if read times out O_o
  echo "$FUNCNAME: Read timed out!"
  return 1
}

#args: handle
function mysql_close {
  local handle
  if ! mysql_check $1; then
    echo "ERROR: Connection not open!"
    return 1
  fi
  handle=(${1//_/ })
  echo "exit;" >&${handle[1]}

  if ! mysql_check $1; then
    return 0
  else
    echo "ERROR: Couldn't close connection!"
    return 1
  fi
}
############### END BASIC MYSQL SESSION IMPLEMENTATION FOR BASH ################################

# Example usage
#define timeout for read command, in case of server error etc.
export MYSQL_READ_TIMEOUT=10

# Connect to db and get $HANDLE
mysql_connect -u mydbuser -d mydb -H mydbserver

#query db and get $DATA
mysql_query $HANDLE "SELECT dt_whatever from tbl_lol WHERE dt_rofl=10"

#close connection
mysql_close $HANDLE

NOTES:

  • Save $HANDLE to a new variable after connection to open as many connections as you like
  • You can't exchange $HANDLE between bash sessions
  • You need linux packages "coproc" "stdbuf" "mysql"
  • Return DATA is of a bash array
$DATA[0] = affected rows/number of sets;
$DATA[1] = number of key=>values pairs following;
$DATA[2] = key1;
$DATA[3] = value1;
      [...]
$DATA[n-1] = keyn;
$DATA[n]   = valuen;
  • Generally all queries should work, even "SELECT count(*)"

Example how to loop the returned data of a two column query

eg. "SELECT dt_id, dt_name FROM ..."

fields=2
for ((i=2; $i<$((${DATA[0]}*${DATA[1]}*$fields)); i+=$((${DATA[1]}*$fields))))
do
    field1key   = ${DATA[$i]};   #this is "dt_id"
    field1value = ${DATA[$i+1]}; #this is the value for dt_id
    field2key   = ${DATA[$i+2]}; #this is "dt_name"
    field2value = ${DATA[$i+3]}; #this is the value  for dt_name
done


Here's a code snippet:

#!/bin/bash

# [...]

set +e # DB locking is not strictly required

# The code in this section tries to ensure that MySQL tables are flushed for a consistent ZFS snapshot.
#
# Use named fifos instead of bash's "coproc mysql -N --unbuffered" as with the latter mysql output is lost after it exits
MI=`mktemp -u -p /root .BKP_LXC_G.XXX` ; MO=`mktemp -u -p /root .BKP_LXC_G.XXX`; rm -f /root/.BKP_LXC_G.*
mkfifo -m 0600 $MI $MO
# Keep the MySQL connection open(and thus the READ lock) until the snapshot is ready
mysql -N --unbuffered <$MI >$MO 2>&1 &
exec 3>$MI ; exec 4<$MO
# Tell MySQL to commit data to disk before snapshotting the FS
echo "SET lock_wait_timeout = 10 ; FLUSH TABLES WITH READ LOCK; SELECT 'MyStrX';" >&3
read -t 15 DB_STR <&4 # This ensures the DB lock has been obtained
if [ "d$DB_STR" != 'dMyStrX' ]; then echo "Error aquiring DB lock: $DB_STR"; fi
set -e ; $SSH zfs snapshot -r $ZFS_LXC@$SNAP_NAME ; set +e
echo "UNLOCK TABLES;" >&3
exec 3>&-
while read -t 1 DB_STR <&4 ; do echo "$DB_STR" ; done # "cat" hangs as it probably does not use NOWAIT
exec 4<&-
rm $MI $MO
set -e

Should not be a problem to clean the fifo's on error by using trap.


This very simple solution for queries which produce one-line results worked for me with GNU bash 5.1 and MariaDB 10.5.

#!/bin/bash

if ! coproc MYSQL { /usr/bin/mysql --batch --silent --unbuffered --database=mydb 2>&1; }; then
        echo 'failed to launch mysql client'
        exit 2
fi

function sqlexec()
{
        local ln
        echo "$1" >&${MYSQL[1]}
        if ! read -t 9 -u ${MYSQL[0]} ln; then
                echo "got no answer from query ($1)" >&2
                return 1
        fi
        if [[ "$ln" =~ ^ERROR ]]; then
                echo "$ln ($1)" >&2
                return 2
        fi
        echo "$ln"
}

if ! sqlexec "SELECT 'sql connection: ok';"; then
        echo 'FAILED check: sql connection'
        exit 2
fi
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜