为何mysqli_fetch_assoc会抛出嵌套循环内的SQL异常?
问题描述
代码示例
$ca=$db->query('SELECT * FROM `clients`'); while ($c=$ca->fetch_assoc()) { try { $mysqli->query('ALTER TABLE `client_'.$c['id'].'_data` ADD COLUMN `updated` datetime DEFAULT NULL'); }catch (Exception $e) { // handling/ignoring the exception } }
场景与问题
部分客户的数据表已存在updated字段,部分则没有。当遇到已存在该字段的表时,会捕获到预期的异常并忽略,但忽略后,循环执行下一次$c=$ca->fetch_assoc()时,脚本会因相同异常崩溃。
报错信息
Fatal error: Uncaught mysqli_sql_exception: Duplicate column ..... Stack trace: #0 /home/api/www/test.php(2): mysqli_result->fetch_assoc() #1 {main} thrown in /home/api/www/test.php on line 2
该异常实际来自循环内的ALTER TABLE查询,并非$ca->fetch_assoc()代码本身。虽然已经捕获异常,但它似乎被存储在mysqli错误栈中,导致下一次fetch_assoc调用时触发崩溃。
不想通过将所有结果存入大数组来避免循环中的fetch_assoc,请问该问题的原因是什么?如何解决?
问题原因与解决方法
原因
mysqli的错误处理机制中,当ALTER TABLE抛出的异常被捕获后,连接的错误状态未被清除。mysqli的结果集操作(比如fetch_assoc)会检查当前连接的错误栈,一旦发现未处理的错误标记,就会直接抛出之前的异常,导致看似和当前操作无关的崩溃。
解决方法
方法1:捕获异常后手动清除错误状态
在catch块中调用$mysqli->clear_error(),手动清空连接的错误栈,确保下一次数据库操作不会触发旧异常:
$ca=$db->query('SELECT * FROM `clients`'); while ($c=$ca->fetch_assoc()) { try { $mysqli->query('ALTER TABLE `client_'.$c['id'].'_data` ADD COLUMN `updated` datetime DEFAULT NULL'); }catch (Exception $e) { // 清除连接的错误状态 $mysqli->clear_error(); // 其他忽略逻辑 } }
方法2:提前判断字段是否存在,避免抛出异常
执行ALTER TABLE前,先查询目标表的结构,确认updated字段不存在再执行添加操作:
$ca=$db->query('SELECT * FROM `clients`'); while ($c=$ca->fetch_assoc()) { $tableName = 'client_'.$c['id'].'_data'; // 查询字段是否存在 $checkRes = $mysqli->query("SHOW COLUMNS FROM `$tableName` LIKE 'updated'"); if (!$checkRes || $checkRes->num_rows === 0) { $mysqli->query("ALTER TABLE `$tableName` ADD COLUMN `updated` datetime DEFAULT NULL"); } }
方法3:使用IF NOT EXISTS语法(MySQL 8.0.19+支持)
如果你的MySQL版本是8.0.19及以上,可以直接在ADD COLUMN时加上IF NOT EXISTS,从根源上避免异常:
$ca=$db->query('SELECT * FROM `clients`'); while ($c=$ca->fetch_assoc()) { $mysqli->query('ALTER TABLE `client_'.$c['id'].'_data` ADD COLUMN IF NOT EXISTS `updated` datetime DEFAULT NULL'); }
内容的提问来源于stack exchange,提问作者Roman Apanovich
相关产品推荐
相关产品推荐

