【发布时间】:2010-11-22 10:06:03
【问题描述】:
我正在编写一个使用mysql 命令行程序与MySQL 数据库交互的bash 脚本。我想在我的 SQL 中使用表锁。我可以这样做吗?
mysql -e "LOCK TABLES mytable"
# do some bash stuff
mysql -u "UNLOCK TABLES"
我问的原因,是因为表锁只为会话保留,所以不会在mysql程序完成后立即释放锁吗?
【问题讨论】:
我正在编写一个使用mysql 命令行程序与MySQL 数据库交互的bash 脚本。我想在我的 SQL 中使用表锁。我可以这样做吗?
mysql -e "LOCK TABLES mytable"
# do some bash stuff
mysql -u "UNLOCK TABLES"
我问的原因,是因为表锁只为会话保留,所以不会在mysql程序完成后立即释放锁吗?
【问题讨论】:
如果LOCK TABLES 和UNLOCK TABLES 之间的命令都是SQL 查询,应该没问题。
但是,在这种情况下,我们为什么不简单地构建一个 SQL 文件并将其通过管道传递给 mysql 命令?
如果在关键部分中除了发出 SQL 查询之外还有其他命令,您可能会遇到麻烦。
将锁定语句发送到文件描述符的echo 命令不会阻塞并等待mysql 响应。
因此,可以在实际获取锁之前执行后续命令。不保证同步。
SYSTEM 命令在 MySQL 服务器上执行。所以要执行的脚本或命令必须存在于同一个 MySQL 服务器上。
您将需要终端访问托管服务器的机器/VM/容器(或至少将脚本传输到服务器主机的方法)。
SYSTEM 命令 also works on Windows as of MySQL 8.0.19,但在 Windows 服务器上运行它当然意味着您将运行 Windows 命令(例如批处理文件或 PowerShell 脚本)。
以下是基于 NVRAM 和 nos 的答案的示例解决方案,但等待锁定:
#!/bin/bash
# creates named pipes for attaching to stdin and stdout of mysql
mkfifo /tmp/mysql.stdin.pipe /tmp/mysql.stdout.pipe
# unbuffered option to ensure mysql doesn't buffer the output, so we can read immediately
# batch and skip-column-names options are for ease of parsing the output
mysql --unbuffered --batch --skip-column-names $OTHER_MYSQL_OPTIONS < /tmp/mysql.stdin.pipe > /tmp/mysql.stdout.pipe &
PID_MYSQL=$!
# make sure to stop mysql and remove the pipes before leaving
cleanup_proc_pipe() {
kill $PID_MYSQL
rm -rf /tmp/mysql.stdin.pipe /tmp/mysql.stdout.pipe
}
trap cleanup_proc_pipe EXIT
# open file descriptors for writing and reading
exec 10>/tmp/mysql.stdin.pipe
exec 11</tmp/mysql.stdout.pipe
# update the cleanup procedure to close the file descriptors
cleanup_fd() {
exec 10>&-
exec 11>&-
cleanup_proc_pipe
}
trap cleanup_fd EXIT
# try to obtain lock with 5 seconds of timeout
echo 'SELECT GET_LOCK("my_lock", 5);' >&10
# read stdout of mysql with 6 seconds of timeout
if ! read -t 6 line <&11; then
echo "Timeout reading from mysql"
elif [[ $line == 1 ]]; then
echo "Lock acquired successfully"
echo "Doing some critical stuff..."
echo 'DO RELEASE_LOCK("my_lock");' >&10
else
echo "Timeout waiting for lock"
fi
上面的例子使用SELECT GET_LOCK() 进入临界区。它为我们生成输出以解析结果并决定下一步做什么。
如果你需要执行不产生输出的语句(例如LOCK TABLES 和START TRANSACTION),你可以在这样的语句之后执行一个虚拟的SELECT 1; 并从标准输出中读取一个合理的超时时间。例如:
# ...
echo 'LOCK TABLES my_table WRITE;' >&10
echo 'SELECT 1;' >&10
if ! read -t 10 line <&11; then
echo "Timeout reading from mysql"
elif [[ $line == 1 ]]; then
echo "Table lock acquired"
# ...
else
echo "Unexpected output?!"
fi
您可能还希望将第三个命名管道附加到 mysql 的 stderr 以处理不同的错误情况。
【讨论】:
没有mkfifo 命令的另一种方法:
cat <(echo "LOCK TABLES mytable;") <(sleep 3600) | mysql &
LOCK_PID=$!
# BASH STUFF
kill $LOCK_PID
我认为 Amr 的答案是最简单的。但是我想分享这个,因为其他人可能也需要一个稍微不同的答案。
sleep 3600 将输入暂停 1 小时。您可以在此处找到其他命令使其暂停:https://unix.stackexchange.com/questions/42901/how-to-do-nothing-forever-in-an-elegant-way
lock tables SQL 立即运行,然后它将等待睡眠计时器。
【讨论】:
我在自己研究这个问题时发现的一个非常有趣的方法是使用 MySQL 的 SYSTEM 命令。我仍然不确定到底有什么缺点(如果有的话),但它肯定适用于很多情况:
例子:
mysql <<END_HEREDOC
LOCK TABLES mytable;
SYSTEM /path/to/script.sh
UNLOCK TABLES;
END_HEREDOC
值得注意的是,这仅适用于 *nix,很明显,as does the SYSTEM command。
感谢 Daniel Kadosh:http://dev.mysql.com/doc/refman/5.5/en/lock-tables.html#c10447
【讨论】:
[编辑]
nos有基本的想法——只运行“mysql”一次,提供的解决方案nos应该可以工作,但是它把FIFO留在了磁盘上。
nos 也是正确的,我搞砸了:一个简单的“echo X >FIFO”将关闭 FIFO;我记错了。我的(删除的)cmets w.r.t.时间不适用,对不起。
也就是说,您不需要 FIFO,您可以使用进程间管道。通过查看我的旧 MySQL 脚本,有些脚本的工作类似于此,但您不能让任何命令写入标准输出(没有一些“执行”技巧)。
#!/bin/bash
(
echo "LOCK TABLES mytable READ ;"
echo "Doing something..." >&2
echo "describe mytable;"
sleep 5
echo "UNLOCK tables;"
) | mysql ${ARGUMENTS}
另一种选择可能是将文件描述符分配给 FIFO,然后让它在后台运行。这非常类似于nos所做的,但“exec”选项不需要子shell来运行bash命令;因此将允许您在“其他东西”中设置“RC”:
#!/bin/bash
# Use the PID ($$) in the FIFO and remove it on exit:
FIFO="/tmp/mysql-pipe.$$"
mkfifo ${FIFO} || exit $?
RC=0
# Tie FD3 to the FIFO (only for writing), then start MySQL in the u
# background with its input from the FIFO:
exec 3<>${FIFO}
mysql ${ARGUMENTS} <${FIFO} &
MYSQL=$!
trap "rm -f ${FIFO};kill -1 ${MYSQL} 2>&-" 0
# Now lock the table...
echo "LOCK TABLES mytable WRITE;" >&3
# ... do your other stuff here, set RC ...
echo "DESCRIBE mytable;" >&3
sleep 5
RC=3
# ...
echo "UNLOCK TABLES;" >&3
exec 3>&-
# You probably wish to sleep for a bit, or wait on ${MYSQL} before you exit
exit ${RC}
注意有几个控制问题:
【讨论】:
这是一种方法,但我相信还有一种更简单的方法..
mkfifo /tmp/mysql-pipe
mysql mydb </tmp/mysql-pipe &
(
echo "LOCK TABLES mytable READ ;" 1>&6
echo "Doing something "
echo "UNLOCK tables;" 1>&6
) 6> /tmp/mysql-pipe
【讨论】:
echo "LOCK TABLES mytable READ ;" 1>&6 是非阻塞的。所以echo "Doing something " 将在它之后执行,无论是否获得锁。