zoukankan      html  css  js  c++  java
  • 14-MySQL DBA笔记-运维技巧和常见问题处理

    第14章 运维技巧和常见问题处理
    DBA的成长,离不开对各种问题的处理。
    本章将为读者介绍一些运维技巧和常见问题的处理方法。
    我们需要意识到,别人的经验代替不了自己的经验,所以,多实践、多处理问题,最终会帮你成为一名训练有素的DBA。

    14.1 MySQL运维技巧
    14.1.1 使用lsof命令恢复文件
    如果你在Linux下不小心删除了一个文件,现在想要恢复这个文件,那么lsof命令就能派上用场了。
    首先补充下关于lsof命令的基础知识。
    lsof是Linux自带的工具,其他Unix系统可能需要自己进行编译安装,
    它可以显示打开的文件和网络连接,以了解关于系统的更多信息,了解应用程序打开了哪些文件或哪个应用程序打开了特定的文件,可以使我们做出更好的决策。
    对于数据库维护,lsof的一个重要作用就是可以帮助我们恢复被删除的文件。
    /proc是一个目录,其中包含了反映内核和进程树的各种文件。
    与lsof相关的信息大多数都存储在以PID(进程ID)命名的目录中,所以/proc/1234中包含的是PID为1234的进程的信息。
    比如我们查看某个MySQL进程的信息,它打开了一个文件func.MYD。
    lsof -p 28400 |egrep "COMMAND|func.MYD"
    COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
    mysqld 28400 mysql 291u REG 8,17 0 65550 /data/to/path/mysql/func.MYD
    我们可以使用“/proc/$pid/fd/$fd”的形式访问文件。
    ll /proc/28400/fd/291
    lrwx------ 1 root root 64 Aug 13 15:25 /proc/28400/fd/291 -> /data/to/path/mysql/func.MYD
    其中,
    $pid是打开文件的进程id。
    $fd是文件描述符,应用程序通过文件描述符识别该文件。
    我们可以使用命令lsof -p "$pid"确认下信息。
    当进程打开某个文件时,只要该进程一直保持打开该文件,即使将文件删除掉,它也依然存在于磁盘中。
    这就意味着,进程并不知道文件已经被删除了,它仍然可以对打开该文件时提供给它的文件描述符进行读取和写入。
    除了该进程之外,这个文件是不可见的,因为已经删除了其相应的目录条目。
    如果可以通过文件描述符查看相应的数据,那么就可以使用I/O重定向将其复制到文件中,如cat /proc/28400/fd/291>/data/to/path/mysql/func.MYD。
    对于许多应用程序,尤其是日志文件和数据库,这种恢复删除文件的方法非常有用。
    如下的案例演示了如何恢复误删除的Apache服务器的access_log。
    1)首先运行如下命令。 lsof |grep access_log
    输出结果类似如下。 httpd 26120 apache 42w REG 253,0 5852 12222531 /apachelogs/access_log (deleted)
    我们可以看到,最后有“(deleted)'”字样,好消息是进程(26120)仍然持有这个文件句柄,如果没有进程打开这个文件,那么我们将永远失去这个文件了。
    2)然后到/proc文件系统下去查找信息。 more /proc/26120/fd/42 以上26120是进程id,42是设备描述符(fd)。
    3)最后,我们可以把这个文件重定向到原来的位置,如下语句。 cat /proc/26120/fd/42 > /apachelogs/access_log
    如果我们不知道进程id,那么我们可以使用如下命令来查找被删除文件的信息。
    lsof -nP | grep '(deleted)' 或者使用如下命令。 find /proc/*/fd -ls | grep '(deleted)'
    仍然以上面的access_log为例,如果你删除了access_log,但你都会发现空间并没有被释放,因为进程仍然持有这个文件。
    这时你可以选择重启Apace服务生效,也可以使用lsof命令找到这个文件,并截断这个文件。
    正常的截断access_log的命令如下所示。 > /path/to/the/file.log
    现在这个文件被我们在操作系统下删除了,那么我们可以用如下的方式来截断它。 > "/proc/$pid/fd/$fd"

    14.1.2 如何删除大文件
    删除一些大文件时,不可避免地会对当前的I/O造成冲击,会对数据库造成许多慢查询。
    特别是上百GB大的文件,影响可能会长达几十秒。
    在ext3系统上,表现尤差。可以采用如下的一些方式来缓解对系统的影响。
    选择闲时,如凌晨,执行删除操作,可使用crontab调度或使用at命令。
    使用其他文件系统,文件系统ext4、xfs对大文件操作的性能远好于ext3。
    使用truncate工具,分段删除文件。
    具体步骤如下。
    1)下载并安装truncate。
    wget http://ftp.gnu.org/gnu/coreutils/coreutils-8.9.tar.gz
    tar -zxvf coreutils-8.9.tar.gz
    cd coreutils-8.9
    ./configure
    make
    cp src/truncate /usr/bin/
    2)如下是一个删除大文件的脚本。
    cat rm_large_file.sh
    #!/bin/bash
    ## 调用 truncate命令删除文件 ,仅针对大文件 (大于几个 GB的文件 )
    ## 调用方式 ./rm_large_file file_name
    if [ "$#" != "1" ] ; then
    echo "please input file name"
    exit 99
    fi
    filename=$1
    filesize=`ls -lh $filename | cut -d -f5| cut -dG -f1`
    # 文件大于 1GB时 ,且必须是数字
    if [[ "${filesize}" == *[!0-9]* ]] ; then
    echo "warning:非数字 ,可能没有 1GB"
    exit 99
    fi
    if [ $filesize -le 1 ];then
    echo "too smalll size"
    exit 88
    fi
    if [ $filesize -ge 500 ];then
    echo "too large size,please modify the shell scripts"
    exit 88
    fi
    sleeptime=3
    echo "truncate file $filename ... ,sleep $sleeptime seconds per truncates"
    date_start=$(date +%s)
    for i in `seq $filesize -1 1 `
    do
    sleep $sleeptime
    echo "truncate to ${i}G"
    truncate -s ${i}G $filename
    done
    rm $filename
    date_end=$(date +%s)
    echo "`date "+%Y-%m-%d %H:%M:%S"` . rm file $filename completed. ($((date_end-date_start)) sec)"

    14.1.3 获取吞吐信息
    如下命令,将实时显示MySQL的吞吐信息。这条命令是从网上摘录的。
    mysqladmin -uroot -p -r -i 2 extended-status |awk -F "|" 'BEGIN { count=0; } { if($2 ~ /Variable_name/ && ++count%15 == 1){print "----------|---------|--- MySQL Command Status --|----- InnoDB row operation -----|-- Buffer Pool Read --"; print "---Time---|---QPS---|select insert update delete| read inserted updated deleted| logical physical";} else if ($2 ~ /Queries/){queries=$3;} else if ($2 ~ /Com_select /){com_select=$3;} else if ($2 ~ /Com_insert /) {com_insert=$3;} else if ($2 ~ /Com_update /){com_update=$3;} else if ($2 ~ /Com_delete /){com_delete=$3;} else if ($2 ~ /InnoDB_rows_read/) {innodb_rows_read=$3;} else if ($2 ~ /InnoDB_rows_deleted/){innodb_rows_deleted=$3;} else if ($2 ~ /InnoDB_rows_inserted/){innodb_rows_inserted=$3;} else if ($2 ~ /InnoDB_rows_updated/){innodb_rows_updated=$3;} else if ($2 ~ /InnoDB_buffer_pool_read_requests/){innodb_lor=$3;} else if ($2 ~ /InnoDB_buffer_pool_reads/) {innodb_phr=$3;} else if ($2 ~ /Uptime / && count >= 2){ printf(" %s |%9d",strftime("%H:%M:%S"),queries);printf("|%6d %6d %6d %6d",com_select,com_insert,com_update,com_delete);printf("|%8d %7d %7d %7d",innodb_rows_read,innodb_rows_inserted,innodb_rows_updated,innodb_rows_deleted); printf("|%10d %11d ",innodb_lor,innodb_phr);}}'

    14.1.4 传输大文件
    迁移或恢复备份的过程有时需要传输大文件,传输大文件时需要注意如下两点。
    1)用scp进行传输的时候,如果可能造成主库所在机器的I/O紧张,那么可能需要考虑限速(-l参数),以免影响数据库主机上的其他实例。
    2)可考虑使用管道,以减少I/O操作,节约时间。
    如下命令将利用管道把文件压缩输出到远程服务器上。
    gzip -c /backup/mydb/mytable.MYD | ssh root@server2 "gunzip -c - > /var/lib/mysql/mydb/mytable.MYD"
    如下命令将利用管道把mysqldump备份的数据输出到远程服务器上。
    mysqldump -uroot db_name | gzip -c | ssh mysql@11.11.11.11 "gunzip -c - > /home/mysql/db_name.sql"
    zcat命令也比较方便实用,可以不用解压缩大文件,直接应用,例如如下命令。 zcat xxx.gz | mysql -uroot -p
    如下命令将合并远程传输和压缩操作,以节省时间。 ssh mysql@11.11.11.11 "cd /home/mysql/data;tar -zcvf - data"| cat > data.tar.gz

    14.1.5 记录连接用户
    我们可以通过设置init_connect参数来记录连接到数据库的用户。
    如下命令中的accesslog表将存储连接的用户。
    mysql> show variables like 'init_connect';
    | init_connect | insert into db_name.accesslog(thread_id,log_time,localname,matchname) values(connection_id(),now(),user(),current_user());
    注意:还需要分配这个程序账号对表accesslog的INSERT权限。
    GRANT INSERT ON `db_name`.`accesslog` TO user_name@'10.%' ;

    14.1.6 如何判断表的碎片
    更连续、更紧凑的数据块可以让性能变得更好。
    碎片化的表会导致一些操作比较慢,如索引范围查找,尤其是对于覆盖索引类的查询。
    数据变得碎片化,可能是出于如下原因:
    行记录自身碎片化,一笔记录被存放在多个地方。
    逻辑顺序的块或行记录并未顺序存储于磁盘中。
    自由空间碎片化。
    MySQL目前并没有足够的信息来帮助我们判断一个表是否有很多碎片,但是我们可以通过其他一些方式来判断。
    一般情况下,当我们对一个大表进行全表扫描的时候,SHOW ENGINE INNODB STATUSG如果显示平均I/O SIZE比较小,比如20KB,
    那么这个表的碎片可能就比较多, 例如如下语句。
    FILE I/O .
    .. reads/s, 20534 avg bytes/read, ... writes/s, ... fsyncs/s
    对大表进行全表扫描,可以使用如下语句进行模拟。 SELECT count(*) FROM tbl WHERE non_idx_col=0
    在操作系统下,我们可以使用cat命令判断碎片是否比较严重,如cat /dev/sdb1>/dev/null。
    下面我们通过一个例子来进行说明。
    正常的情况下,6块15K转速的SAS盘所组成的RAID1+0,I/O吞吐率可以达到300MB每秒,如果我们使用如下命令检查到每秒只有几十MB,那么表的碎片可能比较严重了。
    cat table.ibd > /dev/null 当使用独立表空间时,table.ibd是表的数据文件。
    一般情况下,我们极少碰到表的碎片所导致的性能问题,但在突然的大规模的数据变更下,碎片可能会比较严重。
    一般有如下三种办法整理碎片:
    OPTIMIZE TABLE命令优化。
    ALTER TABLE TABLE_NAME ENGINE=ENGINE。
    重新导出导入数据。
    推荐使用OPTIMIZE TABLE命令进行优化。需要留意的是执行OPTIMIZE TABLE命令时会锁表,你将不能继续写入数据。
    我们也可以借助一些开源的工具来判断数据文件的碎片,对于独立表空间,我们还可以通过查询information_schema.tables的DATA_FREE列来衡量碎片化的程度。
    SELECT ENGINE, TABLE_NAME, Round(DATA_LENGTH/1024/1024) as data_length, round(INDEX_LENGTH/1024/1024) as index_length,
    round(DATA_FREE/1024/1024) as data_free, DATA_FREE/(DATA_LENGTH+INDEX_LENGTH) as ratio_of_fragmentation
    FROM information_schema.tables WHERE DATA_FREE > 0;
    碎片化比较严重,不一定就是有性能问题,即使以上碎片化的比率达到20%甚至30%,你应该在确认性能问题的原因就是表的碎片化后才能采取行动。

    14.1.7 快速关闭MySQL
    如果InnoDB缓冲区(innodb_buffer_size参数)很大,缓冲区内的脏数据太多,那么关闭的时候必须把脏数据刷新到磁盘,这个过程可能会很漫长,从而导致关闭服务的时间过长。
    我们可以临时设置innodb_max_dirty_pages_pct=0,然后等脏数据大部分都刷新到磁盘后
    (查看SHOW ENGINE INNODBSTATUS输出中的Modified db pages,这个值应该比较小),再手动关闭数据库。
    可以采用如下的办法。
    1)运行命令“SET GLOBAL innodb_max_dirty_pages_pct=0;”。
    2)运行命令mysqladmin ext -i 10| grep dirty检查状态变量Innodb_buffer_pool_pages_dirty,等到它接近0的时候关闭它,如果是生产繁忙的系统,这个值可能会一直偏大。
    3)待Innodb_buffer_pool_pages_dirty的值很小时,就可以用mysqladmin关闭MySQL了。
    对于某些需要快速关闭和重启MySQL的情况,这种方法是适合的,因为我们可以预先运行第一个步骤的命令。
    另一种办法是设置innodb_fast_shutdown=2(默认为1,可以动态修改该值),不过不到万不得已时不要这么做,
    因为虽然这样可以快速关闭MySQL,但启动的时候要执行更多的恢复操作。
    注意:对于InnoDB的数据库,FLUSH TABLES是没有用的,FLUSH TABLES是针对MyISAM这类引擎的。

    14.1.8 如何预热数据
    预热数据是为了能把热点数据加载到内存中。
    可以考虑的一个方法是,执行一次全表扫描(full table scan),
    如下是一个全表扫描的例子,在一个4块15K转速的SAS盘所组成的RAID1+0的数据库主机上执行如下查询,查询以一个非索引的列为条件,执行COUNT操作。
    SELECT COUNT(*) FROM tbl WHERE non_idx_col=0;
    通过iostat命令可以看到I/O比较高,顺序读取磁盘吞吐有每秒百MB以上,如果比较低,只有每秒几MB,那么这个表的碎片化可能严重或硬件有问题。
    我们可以使用SHOW ENGINE INNODB STATUS命令检查下预热的效果。
    检查FILE I/O节,可以看到每秒有几百次的I/O,每次I/O在百KB左右。
    这与操作系统命令iostat的输出类似(见avgrq-sz项)。
    检查ROW OPERATIONS节,可以看到每秒有数十万条记录的读取量。
    我们也可以把预热数据要执行的SQL通过init_file参数来执行,这样就可以在系统启动的时候执行了。

    14.1.9 临时禁止数据库访问
    我们可以使用防火墙工具iptables临时禁止网络访问。
    例如如下语句。 iptables -A INPUT -p tcp --dport 3306 -j DROP
    或者配置参数skip-networking临时禁止网络访问。

    14.1.10 获取MySQL连接、用户
    以下查询可用于获取长连接的用户连接。
    SELECT LEFT(host, IF(LOCATE(':', host), LOCATE(':', host), LENGTH(host) + 1) - 1 ) AS host_short,
    GROUP_CONCAT(DISTINCT USER) AS users,COUNT(*)
    FROM information_schema.processlist
    GROUP BY host_short ORDER BY COUNT(*),host_short;

    14.1.11 更改数据库名
    MySQL并没有直接修改数据库名的管理命令,如果需要修改数据库的库名,有如下两种方法。
    使用mysqldump导出该数据库下的所有表,然后创建新的数据库,然后使用mysql命令再把表导入新的数据库,最后删除旧的数据库。
    重命名表,具体步骤如下。
    1)新创建数据库newdb。 mysql> CREATE DATABASE newdb;
    2)生成重命名表的语句。
    mysql -N -e "SELECT CONCAT('rename table olddb.',table_name,' to newdb.',table_name,';') FROM information_schema.TABLES WHERE TABLE_SCHEMA='olddb';" > rename_mysql_name.sql
    3)执行rename_mysql_name.sql。
    mysql -uroot -p < rename_mysql_name.sql
    注意:重命名表的操作会导致连接中断,所以你的应用程序需要有重连的机制。

    14.1.12 批量KILL连接
    有时生产环境突然出现性能恶化,登录MySQL,运行SHOW PROCESSLIST命令,发现有大量查询正在执行,
    这时你打算手动KILL掉应用程序中运行时间超过200s的所有的数据库连接。
    mysql> SELECT CONCAT('KILL ',id,';') FROM information_schema.processlist WHERE user<>'root' AND Command='Query' AND db='db_name' AND time > 200 INTO OUTFILE '/tmp/a.txt';
    mysql> SOURCE /tmp/a.txt;
    如下是一个KILL掉被阻塞的连接的例子,这是一个临时的解决方案,彻底解决问题需要尽快找到导致阻塞的原因。
    for id in `mysqladmin processlist | grep -i locked | awk '{print $1}'`
    do
    mysqladmin kill ${id}
    done

    14.1.13 记录运行时间长的查询
    如下命令将记录运行时间超过120s的查询。
    mysql -uroot -p -e "show full processlist" | grep "Query" |grep "select" | egrep -v "root|Sleep|Locked|INSERT|DELETE|UPDATE" | gawk '{if(strtonum($6)>120){print $0;}}' | grep db_name > /tmp/long_running_process.lst

    14.1.14 删除分表
    如果分表是类似于table_name_20100923这样的格式,现在我们需要删除3个月之前的数据,那么我们可以使用如下语句生成批量DROP TABLE的语句。
    SELECT CONCAT('DROP TABLE ', table_name, ';') INTO OUTFILE '/tmp/file'
    from information_schema.tables
    WHERE table_schema = 'db_name' AND table_name like 'table_name_201%'
    AND table_name < CONCAT('table_name_',date_format(date_sub(now (), interval 90 day),'%Y%m%d'))

    14.2 常见问题
    14.2.1 忘记root密码
    如果忘记了root密码,可以按如下步骤进行处理。
    1)先关闭MySQL服务,你可以使用自启动服务脚本关闭MySQL,或者直接在操作系统下kill掉服务。
    2)然后修改配置文件,添加--skip-grant-tables参数,然后重新启动MySQL服务,此时我们可以无密码登录,然后修改权限表,命令如下。
    UPDATE mysql.user SET password=PASSWORD('new password') WHERE user='root';
    3)修改配置文件,去掉启动参数--skip-grant-tables,重新启动MySQL。这时你就可以使用新密码了。

    14.2.2 InnoDB同时打开事务最大不能超1023个
    对于MySQL 5.1,如果并发事务超过1023个,InnoDB将报错,报错语句为“InnoDB:Warning:cannot find a free slot for an undo log”。
    程序也会报错,报错语句为SQL state[HY000];errorcode[1637];Too many active concurrent transactions;。
    解决方式如下:
    使用MySQL5.5或之后版本。
    使用Percona分支版本也可以解决。

    14.2.3 连接不上MySQL
    如果连接不上MySQL,将输出类似如下的错误信息。
    shell> mysql
    ERROR 2003: Can't connect to MySQL server on 'host_name' (111)
    shell> mysql
    ERROR 2002: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (111)
    可能的原因如下:
    数据库服务器没有启动。
    连接了错误的端口或套接字(socket)文件。
    服务器或客户端程序不具有访问包含套接字文件的目录或套接字文件本身的权限。
    还可以使用shell>netstat -ln | grep mysql来确定下socket文件的位置。
    如果报出如下错误: Access denied for user 'user'@'ip_address' (using password: YES) 那么原因一般是密码错误。
    Access denied错误消息将会告诉你,你正在使用哪个用户句尝试登录,你正在试图连接到哪个主机,是否使用了密码。
    通常,你应该在user表中有一行记录能够正确地匹配错误消息中给出的主机名和用户名。
    例如,如果遇到包含了using password:NO的错误信息,则说明你登录时没有密码。
    如果报出如下错误: Host ... is not allowed to connect to this MySQL server 那么这是因为在user表中没有匹配你运行命令的主机的行,可能是IP被限制了。

    14.2.4 主机的host_name被屏蔽
    如果遇到下述错误,则表示mysqld已收到了来自主机“host_name”的连接请求,但该主机被屏蔽了。
    Host 'host_name' is blocked because of many connection errors. Unblock with 'mysqladmin flush-hosts'
    可运行命令“mysqladmin flush-hosts”解除屏蔽。
    max_connect_errors变量设置了最多允许多少次连接中断,如果超过了这个阈值,MySQL就会屏蔽主机的后续请求。
    直到你执行了mysqladmin flush-hosts命令,或者 发出了FLUSH HOSTS命令为止。

    14.2.5 连接数过多
    当你试图连接到mysqld服务器时遇到“Too many connections”错误,这表示所有可用的连接均已被其他客户端使用。
    允许的连接数由max_connections系统变量来控制。默认值为100。
    如果需要支持更多的连接,则需要设置变量max_connections,命令如下。mysql> SET GLOBAL max_connections = 3000
    mysqld实际上允许max_connections+1个客户端进行连接。额外的连接保留给具有SUPER权限的账户。
    通过为系统管理员而不是普通用户授予SUPER权限(普通用户不应具有该权限),系统管理员能够连接到服务器,
    并使用SHOW PROCESSLIST来诊断问题,即使已连接的无特权客户端数量已达到最大值也同样。
    处理问题的步骤大致如下:
    1)查看当前的连接数Threads_connected,曾经最大的连接数Max_used_connections。
    mysql> SHOW GLOBAL STATUS LIKE '%conn%';
    2)检查下当前线程的详细信息,线程是否大量累计,被阻塞。
    mysql> SHOW PROCESSLIST ;
    以上步骤,一般可以判断原因,必要的话,可以运行KILL命令,临时KILL线程。
    3)如果需要临时增加连接数阈值,可运行如下命令。 mysql> SET GLOBAL max_connections=new_value;
    4)如果需要永久变更,则要记得同步更改配置文件my.cnf。

    14.2.6 处理磁盘满
    出现磁盘空间满的情况时,MySQL将会每分钟检查一次,查看是否有足够的空间写入当前行。
    如果有足够的空间,则将继续,就像什么也未发生一样。
    每10分钟会将1个条目写入日志文件,提醒磁盘满状况。
    在磁盘满的情况下,可以临时把一些大文件挪走,比如二进制日志文件,如果不需要马上切换日志,一般是不会有问题的。

    14.2.7 表损坏
    表损坏主要出现在MyISAM引擎的表发生损坏的情况下,如果MySQL主机突然崩溃,或者强制关机而没有正常关闭MySQL服务都可能导致MyISAM表损坏。
    当在表中查询数据时候,你会碰到报错。
    一个被损坏了的表的典型症状如下:
    1)当在从表中选择数据时,你会得到如下错误。 Incorrect key file for table: '...'. Try to repair it
    注意:磁盘空间,如临时表所在的操作系统分区满了的情况下,也会有这种报错。
    2)查询不能在表中找到行,或者返回不完全的数据。
    3)提示错误信息:Error:Table 'p' is marked ascrashed and should berepaired。
    4)打开表失败:Can't open file。
    MyISAM表可以采用以下步骤进行修复:
    1)使用REAPAIR TABLE命令或myisamchk工具来修复。
    2)如果上面的方法修复无效,则使用备份来恢复表。
    建议在配置文件里添加自动修复表的参数,即myisam-recover=default,这样系统会在启动的时候自动帮你修复表。
    如果表被标记为“not closed properly”或“crashed”,那么MySQL会检查该表,并写入信息“Warning:Checking table...”到错误日志,
    如果表需要修复,则执行修复,并写入“Warning:Repairing table”信息到错误日志。
    如果你的MySQL实例没有崩溃过,但是出现了大量的这类信息,那么可能是哪里出问题了,你需要做进一步的诊断。
    如果是大表,修复表会很耗时,还可能会影响到服务。

    14.2.8 查看锁的等待
    新的MySQL版本5.5增加了一些视图,用于查看锁的等待情况,例如:
    SELECT r.trx_id AS waiting_trx_id, r.trx_MySQL_thread_id AS waiting_thread,TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP) AS wait_time,
    r.trx_query AS waiting_query, l.lock_table AS waiting_table_lock, b.trx_id AS blocking_trx_id, b.trx_MySQL_thread_id AS blocking_thread,
    SUBSTRING(p.host, 1, INSTR(p.host, ':') - 1) AS blocking_host, SUBSTRING(p.host, INSTR(p.host, ':') +1) AS blocking_port,
    IF(p.command = "Sleep", p.time, 0) AS idle_in_trx, b.trx_query AS blocking_query
    FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS AS w
    INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS b ON b.trx_id = w.blocking_trx_id
    INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS r ON r.trx_id = w.requesting_trx_id
    INNER JOIN INFORMATION_SCHEMA.INNODB_LOCKS AS l ON w.requested_lock_id = l.lock_id
    LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST AS p ON p.id = b.trx_MySQL_thread_id
    ORDER BY wait_time DESC
    或,
    SELECT CONCAT('thread ', b.trx_MySQL_thread_id, ' from ', p.host) AS who_blocks, IF(p.command = "Sleep", p.time, 0) AS idle_in_trx,
    MAX(TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW())) AS max_wait_time, COUNT(*) AS num_waiters
    FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS AS w
    INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS b ON b.trx_id = w.blocking_trx_id
    INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS r ON r.trx_id = w.requesting_trx_id
    LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST AS p ON p.id = b.trx_MySQL_thread_id
    GROUP BY who_blocks ORDER BY num_waiters DESCG
    也可以使用mysqladmin debug命令查看锁的等待情况,mysqladmin debug命令将会把锁的信息打印到MySQL Server的错误日志(error log)中。

    14.2.9 mysqldump备份报错
    将mysqldump备份到远程管道,或者慢速设备(如NFS)中时,可能会出现如下的报错信息。 "Got timeout writing communication packets".
    或者报错信息如下:110421 2:07:01 [Warning] Aborted connection 237201 to db: 'db_01' user: 'root' host: 'localhost' (Got timeout writing communication packets)
    你可能需要增加net_write_timeout参数才可以确保不会出错。

    14.2.10 Table 'tbl_name' doesn't exist
    由于MySQL是使用目录和文件来保存数据库和表的,因此如果它们位于区分文件名大小写的文件系统上时,数据库和表名也区分文件名大小写。
    如果提示如下错误:Table 'tbl_name' doesn't exist. Can't find file: 'tbl_name' (errno: 2) 则有可能确实不存在这个表,但也可能表是存在的,但你没有正确引用它,或者没有权限。

    14.2.11 root账号权限异常
    如果root账号异常,比如,误删除了root账号,那么你可以采取下面的方式来处理,建议不到万不得已时,不要使用下面的方法。
    更保险的办法还是关闭实例, 然后直接复制其他实例的mysql库,重启然后进行适当的修改即可。
    恢复root账号的具体步骤如下。
    1)关闭MySQL。
    2)不加载权限表启动MySQL,即运行mysqld_safe --skip-grant-tables &。
    3)运行mysql -u root -p回车,给mysql.user表中插入一条记录(root账户)。
    INSERT INTO `user` (`Host`, `User`, `Password`, `Select_priv`, `Insert_priv`, `Update_priv`, `Delete_priv`, `Create_priv`, `Drop_priv`, `Reload_priv`, `Shutdown_priv`, `Process_priv`, `File_priv`, `Grant_priv`, `References_priv`, `Index_priv`, `Alter_priv`, `Show_db_priv`, `Super_priv`, `Create_tmp_table_priv`, `Lock_tables_priv`, `Execute_priv`, `Repl_slave_priv`, `Repl_client_priv`, `Create_view_priv`, `Show_view_priv`, `Create_routine_priv`, `Alter_routine_priv`, `Create_user_priv`, `ssl_type`, `ssl_cipher`, `x509_issuer`, `x509_subject`, `max_questions`, `max_updates`, `max_connections`, `max_user_connections`)
    VALUES('localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','','0','0','0','0');
    4)然后,重启mysqld即可。
    有时我们可能会发现root不能给其他用户赋予权限。
    mysql> GRANT EVENT ON *.* to event_user@localhost;
    ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
    SELECT host,user,Grant_priv,Super_priv FROM mysql.user WHERE user='root'; #可发现 root@localhost的 Grant_priv值为 N
    如下命令将给root账号恢复GRANT权限。
    UPDATE mysql.user SET Grant_priv='Y', Super_priv='Y' WHERE user='root' and host='localhost'; FLUSH PRIVILEGES;
    mysql > EXIT
    mysql > GRANT EVENT ON *.* to event_user@localhost;

    14.2.12 SHOW PROCESSLIST输出中有大量unauthenticated user连接
    SHOW PROCESSLIST输出中有大量unauthenticated user连接时,如果连接很频繁,则客户端可能会报错“Can't connect to MySQL server on”。
    一般出现这种异常的原因是,数据库开了域名反向解析从而导致客户端连接超时。
    解决方案如下:
    1)把服务的DNS反向解析功能关掉。
    2)构建自己的DNS解析或更改hosts文件,使其能够快速解析域名。

    14.2.13 统计information_schema里面的元数据信息缓慢
    有时统计information_schema里面的元数据信息缓慢,这种情况一般发生在统计许多表、大表或分区表的时候,
    对information_schema执行的一些查询,如SHOW TABLE STATUS、SHOW INDEX等操作,会导致MySQL Server计算统计信息。
    由于查询information_schema里的信息缓慢,甚至还可能会导致服务器出现性能问题,
    许多人改用操作系统命令行工具进行统计,比如使用find、du等命令统计空间占用情况等。
    由于可能会影响到服务器性能,因此对information_schema的查询要慎重使用,对于拥有大量数据的MySQL Server可能会导致严重的性能问题。
    一些监控工具、监控脚本就存在这样的严重问题。
    解决办法是设置变量SET GLOBAL innodb_stats_on_metadata=0以避免产生性能问题。
    innodb_stats_on_metadata=0表示在查询information_schema时,不自动更新统计数据。
    InnoDB的统计信息并不是持久化到硬盘里的,而是动态收集的,存储在内存中的。
    MySQL 5.6、Percona Server可以设置参数,对统计数据进行持久化。

    14.2.14 Aborted_connects、Aborted_clients异常升高
    有时我们会观察到状态变量Aborted_connects、Aborted_clients在不断增长。
    mysqladmin ext | grep Abort
    mysqladmin ext | grep Abort | grep -v 0
    用“--log-warnings=2”选项启动mysqld,可获得关于连接的更多信息。
    这样,就能将某些断开连接错误记录到hostname.err文件中,例如如下语句。
    010301 14:38:23 Aborted connection 854 to db: 'users' user: 'josh'
    你也可以动态设置这个参数。
    如果客户端成功连接到服务器但是因异常断开了连接,
    那么MySQL Server的状态变量Aborted_clients将增加,并将“Aborted connections”(放弃连接)消息记录到错误日志中,可能的原因有如下几点。
    客户端程序在退出之前未调用mysql_close()。
    客户端的空闲时间超过wait_timeout或interactive_timeout秒,未向服务器发出任何请求。
    客户端在数据传输中途突然结束。
    如果客户端甚至不能连接到MySQL Server,那么MySQL Server将会增长Aborted_connects变量,不成功的连接尝试可能是因为如下的原因。
    客户端没有权限连接数据库。
    客户端密码错误。
    连接信息包不含正确的信息。
    获取连接信息包的时间超过connect_timeout秒。
    我们可以使用tcpdump来获取可能出错的原因,比如可以用如下的方式检测到密码错误。 tcpdump -s 1500 -w tcp.out port 3306 strings tcpdump.out

    14.2.15 MySQL server has gone away错误
    有时会出现“MySQL server has gone away”的报错,一般同时还会有“Lost connection to server during query”的报错。
    发生MySQL server has gone away的最常见原因是连接闲置超时,被服务器中断连接,
    默认情况下,服务器关闭空闲时间超过8小时的连接,我们可以设置变量wait_timeout,改变8小时的默认值,一般同时还需要修改 interactive_timeout。
    mysql命令行默认是重连的,但有一些应用程序,也许并没有重连的机制,这往往会导致执行失败。
    导致MySQL server has goneaway错误的一些其他原因如下所示:
    使用KILL语句或mysqladmin kill命令杀死了正在运行的线程。
    在关闭了与服务器的连接后试图运行查询。这表明应更正应用程序中的逻辑错误。
    在客户端的一侧遇到TCP/IP连接超时错误。
    在服务器端遇到超时错误,而且禁止了客户端中的自动再连接功能。
    如果向服务器发出了不正确或过大的查询,也会遇到这类问题。
    如果mysqld收到过大的或无序的信息包,它会认为客户端出错,并关闭连接。
    如果需要执行较大的查询(例如,正在处理大的BLOB列),则可通过设置服务器的max_allowed_packet变量,增加查询限制值,该变量的默认值为1MB。

    14.2.16 信息包过大错误
    通信信息包是发送至MySQL服务器的单个SQL语句,或者发送至客户端的单一行。
    在MySQL 5.1服务器和客户端之间最大能发送的信息包为1GB。
    当MySQL客户端或mysqld服务器收到大于max_allowed_packet字节的信息包时,将发出“(ER_NET_PACKET_TOO_LARGE)”错误,并关闭连接,
    错误如下。 Error: 1153 SQLSTATE: 08S01 (ER_NET_PACKET_TOO_LARGE) Message: Got a packet bigger than ‘ max_allowed_packet' bytes
    有一些客户端还会在包过大时,提示“Lost connection to MySQL server during query”的错误。
    客户端和服务器均有自己的max_allowed_packet变量,因此,如你打算处理大的信息包,则必须增加客户端和服务器上的该变量。
    如果你正在使用mysql客户端程序,这时要想将max_allowed_packet变量设置为较大的值32M,可用下述方式进行修改。
    mysql> set global max_allowed_packet=32*1024*1024;
    配置文件可修改如下:
    [mysqld]
    max_allowed_packet=32M
    增加该变量的值很安全,这是因为仅当需要时才会分配额外的内存。
    例如,仅当你发出长查询或mysqld必须返回大的结果行时mysqld才会分配更多的内存。
    该变量之所以取较小的默认值也是一种预防措施,以捕获客户端和服务器之间的错误信息包,并确保不会因为偶然使用大的信息包而导致内存溢出。

    14.2.17 内存溢出
    32位机器中有内存寻址的限制,注意不要突破2GB(一般32位系统有2.5~2.7GB的限制)的限制,否则很容易导致MySQL崩溃,如下是一段崩溃时候的报错信息。
    "100201 11:49:29 [ERROR] /usr/local/mysql/bin/mysqld: Out of memory (Needed 2095208 bytes)"
    100108 10:42:12 [ERROR] /usr/local/mysql/bin/mysqld: Out of memory (Needed 2095392 bytes)
    100108 18:30:10 [ERROR] /usr/local/mysql/bin/mysqld: Out of memory (Needed 156 bytes)
    100108 18:30:10 – mysqld got signal 11 ;
    如果使用mysql客户端程序发出了查询,并收到下述错误之一,则表示mysql没有足够的内存来保存全部查询结果。
    mysql: Out of memory at line 42, 'malloc.c'
    mysql: Out of memory at line 42, 'malloc.c'
    mysql: needed 8136 byte (8k), memory in use: 12481367 bytes (12189k)
    ERROR 2008: MySQL client ran out of memory

    14.2.18 MySQL单张表为多大才合适,为什么大表会慢
    笔者建议单个表的数据在千万条以下,主要是因为MySQL5.0和MySQL 5.1在线DDL的能力太弱,
    MySQL 5.6和5.7对于在线表结构的变更做了许多优化,已经极大地缓解了修改表结构对于生产系统的影响。
    性能往往不是制约数据量的主要因素,如果你修改表结构的代价比较高,而你的磁盘性能并不高,那么你就应该未雨绸缪,把数据表限制得小一些。
    如果你能够确保修改表结构并没有太大的影响,那么几亿以上条数据的表也是可以接受的。
    生产环境中,研发人员往往担心表太大了性能会下降,但是性能下降,往往是受多个因素的影响,
    如果优化得好,资源配置适当,表的设计和访问充分利用了MySQL的簇表结构,
    比如,访问是基于主键,或者基于主键的范围查找,那么亿条数据级别的表也是可以很快的。
    InnoDB缓冲很重要,如果我们的热点数据能够被缓存,当我们对大表的访问能够命中缓冲时,那么性能显然也会很好。
    对于一些大表的访问,如果随机读过多,那么也可能会导致严重的性能问题,有时顺序读可能还会更快些。
    顺序读,意味着我们选择的是全表扫描或基于主键的范围查找。
    项目初期,研发人员对于数据访问的模式可能还不太了解,设计了比较符合范式的表,那么查询数据时往往需要连接多张表,在数据量不大的情况下,这点可能不成为问题,
    但是一旦表的数据量增加了,连接的代价就会越来越大,因为利用索引连接表,往往意味着大量的随机读。
    所以在OLAP这种存在很多大表的应用中,应尽量避免出现连接。
    清理大表的数据时,归档数据也是一种可以考虑的方式,我们可以把历史表分离到更差的机器或磁盘中。
    对于OLTP应用,如果必须对大量数据进行操作,那么分批地小批量获取数据将会更佳,因为MySQL不擅长同时处理大量短小的事务和一个巨大的事务。

    14.2.19 MySQL最大能支持多大的并发查询
    对于普通的数据库主机(硬盘采用SSD),一般简单的查询(读写混合)可以达到3000~5000 QPS(高并发的小结果集)。
    如果是纯基于主键的查询,则QPS可以更高。
    但如果是复杂的查询,可能就只会有几百的QPS。
    复杂的查询是指诸如分组、排序、批量操作数据、统计之类的消耗资源的查询,或者会涉及复杂的计算。
    你可以尝试分解复杂的查询,把一些排序、计算之类的操作放到应用程序中去实现。

    14.2.20 创建索引出错
    创建索引可能会报错“ERROR 1170(42000)”。 如果是对BLOB或TEXT字段建立索引,则需要设置键的长度,否则会出错。
    mysql> create index idx_col on table_name(col);
    ERROR 1170 (42000): BLOB/TEXT column 'col' used in key specification without a key length
    正确的做法是,设置键的长度 mysql> create index idx_col on table_name(col(255));

    14.3 故障和性能问题处理
    14.3.1 通过减少文件排序和临时表提高性能
    通过检查SHOW PROCESSLIST输出或慢查询日志,我们可以筛选出开销大的SQL,
    通过EXPLAIN检查它们的执行计划,我们会发现它们可能扫描了过多的记录数,
    并且Extra列的输出类似于“Using where;Using temporary;Using filesort”。
    Using filesort意味着你不能利用索引进行排序,
    Using temporary意味着查询使用了临时表来存储数据,如果临时表超过了限制,那么还可能需要转变成磁盘临时表,在高并发的情况下,可能还会导致严重的性能问题。
    如果通过SHOW PROCESSLIST看到有输出“Copying to tmp table on disk”的信息, 则应该避免,即使当时没有出现性能问题,以后也可能会导致性能问题。
    如果我们把Using temporary和filesort优化掉了,往往也就解决了性能问题。
    对于临时表的优化,请参考6.2.10节,对于filesort的优化,解决的思路就是尽量利用索引进行排序,如果实在不能利用索引排序,可以通过限制排序集合的数据来缓解性能问题。
    我们在6.2.8节对filesort的优化做了详细介绍。

    14.3.2 通过慢查询快速定位导致性能问题的SQL
    我们在4.3节详细介绍了慢查询日志。
    普通情况下,我们通过检查慢查询日志里扫描的记录数,返回的结果集及响应的时间可以大致判断出不良SQL。
    一般在没有严重性能问题的时候进行检查会更好,因为这个时候,还没有出现SQL互相等待,资源严重竞争的问题,一旦出现互相等待,慢查询的大量输出往往会扭曲分析结果。
    对于慢查询日志里出现了太多条目这种情况,我们可以通过一些方法筛选出更值得怀疑的SQL,
    扫描记录数非常多的慢查询记录,仍然是重点怀疑的对象,如果并发还比较多的话,则更值得怀疑。
    检查出现初期性能问题的慢查询,干扰会更少,另外,往往在一个严重消耗资源的不良SQL执行完后,会马上出现大量的慢查询,因为之前的这些本来应该运行得很快的SQL被阻塞了。
    如何定位到具体的不良SQL,需要经验和技巧,通过不断地累计经验,你会越来越熟悉通过慢查询快速定位问题,
    如果有自动化的收集信息的工具会更好,你可以定期扫描慢查询日志,记录这些慢查询日志,通过人工或自动的方式分析和报警。

    14.3.3 定位导致了性能问题的客户端/应用服务器
    许多时候,我们碰到的性能问题都是来自于一些特定的应用服务器,这个时候,要求我们能够快速定位到连接到MySQL的可疑应用服务器,
    SHOW PROCESSLIST输出中的IP信息会帮助我们找到可疑的应用服务器,甚至可疑的应用服务,
    比如,我们在host列看到的信息不仅包括IP信息也包括端口信息: 192.168.1.70:45384,
    通过在IP信息所指的应用服务器中运行lsof或netstat命令,我们可以找到对应的操作系统进程ID,如下所示。
    netstat -ntp | grep :45384
    tcp 0 0 192.168.1.70:45384 192.168.1.82:3306 ESTABLISHED 28540/php-cgi
    以后就可以对进程id为28540的php-cgi进程做更多的诊断。
    如果发现我们的数据库连接里有大量的sleep状态的连接,那么可以使用如上的方式找到对应应用服务器上的服务。

    小结:
    本章介绍了运维等一些技巧及常见问题的处理。
    在日常运维过程中,建议大家平时积累自己的知识库,记录下故障处理过程中的现象、影响、 处理措施、原因分析、后续改善等信息。
    一些小小的应用技巧在关键的时候很可能会帮到你的大忙。
    随着你经验的不断丰富,将逐渐能够避免大部分可能出现的问题。
    成熟的运维体系和训练有素的工程师团队面对的MySQL问题将不会是本书所列举的一些常见小问题,
    如果你所在的公司业务规模扩充得很快,那么你遇到的将更多的是性能、可扩展性方面的问题。
    笔者将在最后一篇,性能优化和架构篇,着重讲述这些内容。

  • 相关阅读:
    网页的摘要信息
    自我介绍
    DIV和SPAN的区别
    软件工程简介
    设置层的漂移
    构建之法现代软件工程
    手机验证码的相关知识
    Python2和Python3的区别
    我的第一个练习
    结对编程后传之做汉堡
  • 原文地址:https://www.cnblogs.com/BradMiller/p/12036183.html
Copyright © 2011-2022 走看看