1 发现问题
一直好好的 MySQL 数据库,今天突然报错了,是用户在查询数据时发现的(web 应用)。赶紧看看应用日志:
Caused by: java.sql.SQLException: Got error 28 from storage engine
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1078)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4237)
at com.mysql.jdbc.MysqlIO.nextRowFast(MysqlIO.java:2190)
at com.mysql.jdbc.MysqlIO.nextRow(MysqlIO.java:2046)
at com.mysql.jdbc.MysqlIO.readSingleRowSet(MysqlIO.java:3543)
at com.mysql.jdbc.MysqlIO.getResultSet(MysqlIO.java:491)
at com.mysql.jdbc.MysqlIO.readResultsForQueryOrUpdate(MysqlIO.java:3245)
at com.mysql.jdbc.MysqlIO.readAllResults(MysqlIO.java:2413)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2836)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2825)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2156)
at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2323)
at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:2712)
at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_executeQuery(FilterEventAdapter.java:465)
at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:2709)
at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_executeQuery(FilterEventAdapter.java:465)
at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:2709)
at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.executeQuery(PreparedStatementProxyImpl.java:132)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.executeQuery(DruidPooledPreparedStatement.java:227)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:56)
... 139 more
2 原因分析
2.1 分析源代码
根据出错日志的堆栈信息,可以看出是 MySQL 在验证操作系统返回的响应包时报的错(MysqlIO.checkErrorPacket) ,打开源代码跟踪看看:
private void checkErrorPacket(Buffer resultPacket) throws SQLException {
int statusCode = resultPacket.readByte();
// Error handling
if (statusCode == (byte) 0xff) {
String serverErrorMessage;
int errno = 2000;
if (this.protocolVersion > 9) {
errno = resultPacket.readInt();
String xOpen = null;
serverErrorMessage = resultPacket.readString(this.connection.getErrorMessageEncoding(), getExceptionInterceptor());
if (serverErrorMessage.charAt(0) == '#') {
// we have an SQLState
if (serverErrorMessage.length() > 6) {
xOpen = serverErrorMessage.substring(1, 6);
serverErrorMessage = serverErrorMessage.substring(6);
if (xOpen.equals("HY000")) {
xOpen = SQLError.mysqlToSqlState(errno, this.connection.getUseSqlStateCodes());
}
} else {
xOpen = SQLError.mysqlToSqlState(errno, this.connection.getUseSqlStateCodes());
}
} else {
xOpen = SQLError.mysqlToSqlState(errno, this.connection.getUseSqlStateCodes());
}
clearInputStream();
StringBuilder errorBuf = new StringBuilder();
String xOpenErrorMessage = SQLError.get(xOpen);
if (!this.connection.getUseOnlyServerErrorMessages()) {
if (xOpenErrorMessage != null) {
errorBuf.append(xOpenErrorMessage);
errorBuf.append(Messages.getString("MysqlIO.68"));
}
}
errorBuf.append(serverErrorMessage);
if (!this.connection.getUseOnlyServerErrorMessages()) {
if (xOpenErrorMessage != null) {
errorBuf.append("\"");
}
}
appendDeadlockStatusInformation(xOpen, errorBuf);
if (xOpen != null && xOpen.startsWith("22")) {
throw new MysqlDataTruncation(errorBuf.toString(), 0, true, false, 0, 0, errno);
}
throw SQLError.createSQLException(errorBuf.toString(), xOpen, errno, false, getExceptionInterceptor(), this.connection);
}
serverErrorMessage = resultPacket.readString(this.connection.getErrorMessageEncoding(), getExceptionInterceptor());
clearInputStream();
if (serverErrorMessage.indexOf(Messages.getString("MysqlIO.70")) != -1) {
throw SQLError.createSQLException(SQLError.get(SQLError.SQL_STATE_COLUMN_NOT_FOUND) + ", " + serverErrorMessage,
SQLError.SQL_STATE_COLUMN_NOT_FOUND, -1, false, getExceptionInterceptor(), this.connection);
}
StringBuilder errorBuf = new StringBuilder(Messages.getString("MysqlIO.72"));
errorBuf.append(serverErrorMessage);
errorBuf.append("\"");
throw SQLError.createSQLException(SQLError.get(SQLError.SQL_STATE_GENERAL_ERROR) + ", " + errorBuf.toString(), SQLError.SQL_STATE_GENERAL_ERROR, -1,
false, getExceptionInterceptor(), this.connection);
}
}
这些代码都是为了对操作系统返回的错误代码信息,进行转义,转换为 MySQL 自己的错误信息。所以 SQLError 一定就是 MySQL 的错误信息字典咯!打开一看,果然是这样:
mysqlToSqlState = new Hashtable<Integer, String>();
mysqlToSqlState.put(MysqlErrorNumbers.ER_SELECT_REDUCED, SQL_STATE_WARNING);
mysqlToSqlState.put(MysqlErrorNumbers.ER_WARN_TOO_FEW_RECORDS, SQL_STATE_WARNING);
...
mysqlToSqlState.put(MysqlErrorNumbers.ER_LOCK_DEADLOCK, SQL_STATE_ROLLBACK_SERIALIZATION_FAILURE);
MysqlErrorNumbers 是 MySQL 定义的错误代码常量类,里面可以找到 Got error 28 from storage engine 定义的出处:
public final static int ER_GET_ERRNO = 1030; //SQLSTATE: HY000 Message: Got error %d from storage engine...
有的人会发现这个 ER_GET_ERRNO 根本就没有被其他代码调用过!个人猜测是因为 MySQL 的 jar 源代码与实际的 jar 版本不一致造成的。
2.2 神秘的 “28”
那么 “28” 究竟又是什么意思呢?
在 MySQL 官网有这样的一个问题解答,翻译如下:无法创建或者写数据到文件怎么办?
如果在执行 SQL 查询时,会得到下列错误,那就意味着 MySQL 无法在临时目录上创建临时文件(数据的结果集需要用到临时文件)。
Can't create/write to file '\\sqla3fe_0.ism'.
windows 经常会出现这样的提示,Unix 的提示与 windows 类似。
修复方法是使用 mysqld 方法,重新指定一个有权限文件夹,像这样:
[mysqld]
tmpdir=C:/temp
C:/temp
必须存在并且有足够的磁盘空间供 MySQL 使用。
没有权限也会引发这个问题,所以必须确保 MySQL 有权限写 tmpdir 文件夹。
也可以使用 perror 命令来确定错误代码的含义。还有一个原因是磁盘空间满了:
shell> perror 28
OS error code 28: No space left on device
看到了没有,这就是 28 的真正含义!
查了一下 linux 的错误码中 28 的含义,也是 No space left on device!
现在清楚了吧 O(∩_∩)O~
3 解决问题
linux 中先查询磁盘空间使用量:
myServer# df -h
肯定有一个文件系统是 100%,可能像这样:
Filesystem Size Used Avail Capacity Mounted on
/dev/vdisk 13G 13G 46M 100% /
devfs 1.0k 1.0k 0B 100% /dev
剩下的事情就简单咯,删除无用文件;把大文件移动到其他文件系统;甚至在 MySQL 中重新指定临时文件目录的路径…这些方法目的都是为了把磁盘空间给腾出来 O(∩_∩)O~