一、面向对象
<?php
$mysqli =new mysqli("localhost", "my_user", "my_password", "world")//实例化对象
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error())
exit()
}
printf("Host information: %s\n", $mysqli->host_info)
/* close connection */
$mysqli->close()
?>
二、面向过程
<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "world")
/* check connection */
if (!$link) {
printf("Connect failed: %s\n", mysqli_connect_error())
exit()
}
printf("Host information: %s\n", mysqli_get_host_info($link))
/* close connection */
mysqli_close($link)
?>
在项目的一次需求中,需要对一个表增加字段,然而在执行增加字段的sql语句时,卡住了很久都没提交到Mysql完成,而此时对外接口服务请求也卡住了,这时中断卡住的alter table 语句,服务慢慢恢复正常,如果不搞清楚这个问题的根源,不敢增加字段,因为会直接影响到服务
通过show processlist 查看到在alter table语句执行卡住过程中,累计了大量状态为 Waiting for table metadata lock 的记录
然后查看当前的事务状态 执行 select * from information_schema.innodb_trx\G
发现了其中一条已经运行了很久的事务,我怀疑跟这个运行很久的而且没有提交的事务有关。
在本地mysql开多个终端测试
session 1: 开启事务,执行select 语句,但不提交事务
session 2:执行增加字段sql
执行被阻塞了
可以看到alter table语句的状态为Waiting for table metadata lock
session 3 : 再次查询t1表
也被阻塞了
select * from t1 再次查询t1表也是 Waiting for table metadata lock状态,说明由于 metadata lock的存在,会导致后面正常的查询都会因为等待锁而阻塞
再查看当前事务运行状态:
可以看到,session1的事务由于还没提交,所以这里能看到它的状态还是running
这时我们commit session1的事务,看看效果
session 1:
session 2:
session 3:
可以看到session1的事务提交后,session2 和session3 都正常执行了, 他们完成的时间分别是30秒和7秒
通过上面的还原测试,可以知道是由于事务没有提交而给表加了锁,导致后面alter语句因为等待锁而阻塞,从而影响后面的正常请求。
那说明我们的项目是默认开启了事务吗?
继续排查,项目是使用flask-sqlchemy的插件来管理mysql接入,然后查了下文档
在实例化sqlchemy的时候,会创建一个用于跟Mysql交互的session对象,看看源码
从 SignallingSession类的定义看来,autocommit=False,说明默认都给所有的sql执行开启事务,也就是说,哪怕是纯select语句,不需要加锁的select,我们的项目默认也需要开启事务,这对于Mysql MVCC的版本控制来说,是没必要的。
解决办法:就是在实例化SQLAlchemy的时候,给一个参数,修改的session的autocommit=True:
来自官网的介绍:
意思就是为了保证事务的串行执行,而启用的一个锁,这个锁只会在事务结束的时候释放,因此在事务提交或回滚钱,任何对这个表做的DDL *** 作,都是会阻塞的
这个 Metadata lock 是MySQL在5.5.3版本后引入了,为的是防止5.5.3以前的一个bug的出现:
当一个会话在主库执行DML *** 作还没提交时,另一个会话对同一个对象执行了DDL *** 作如drop table,而由于MySQL的binlog是基于事务提交的先后顺序进行记录的,因此在从库上应用时,就出现Q了先drop table,然后再向table中insert的情况,导致从库应用出错。
表对象缓存: 是将某个表对象的字典信息(定义内容)缓存到内存中,用来提高对表的访问效率。某个表被访问过一次后,只要服务器没有关闭且表定义没有被修改的条件下,访问该表,只需要从内存中找到这个已经缓存起来的对象做相应 *** 作即可。
用户访问表时,表对象在缓存时: 通过HASH算法找到TABLE_SHARE,然后每个线程构造各自的实例化TABLE即可。
用户访问表时,当表没有被缓存的情况下: 第一需要打开表,首先需要从系统表中将这个表的所有信息都读入内存中,这些信息包括表名、库名、所有列信息、列的默认值、表的字符集、对应的frm文件路径、所属存储引擎、主键等,将这些信息构造一个TABLE_SHARE结构体,这个结构体是表对象缓存的第一层,所有用户共享访问且为静态不允许修改,它是缓存在table_def_cache(由参数table_definition_cache控制)中的。
而真正与用户打交道的是TABLE_SHARE的衍生品,它对应结构体为TABLE,在被使用前需要将TABLE_SHARE结构体实例化TABLE才能被使用,由每个线程构造各自的实例化TABLE即可。(实例化的TABLE由table_open_cache及table_open_cache_instance控制)
注意1: DDL *** 作时会将所有instance锁住,而DML *** 作时instance之间互不干扰。
(DDL statements still require a lock on the entire cache, but such statements are much less frequent than DML statements.)
注意2: 一个线程中如果打开表过多,超过一个instance限制的大小时,是不能跨instance缓存的
(instance大小:table_open_cache / table_open_cache_instances)
表缓存涉及其他参数: 通过下面参数判断table_open_cache参数设置是否合理
table_open_cache_hit:能够从table open cache的free list中找到table则为命中,+1
table_open_cache_misses:与table_open_cache_hit相反,如果找不到则需要重新实例化则+1,通常发生在初始化第一次加载表或超过table_open_cache的设置被淘汰后需要重新实例化。
table_open_cache_overflow:table cache淘汰的数量,每次淘汰+1
opened_tables:已经打开的表数。如果Opened_tables很大,那么table_open_cache的值可能太小了。
open_tables:总的instance (table cache)的总数
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)