29-如何判断一个数据库是不是出问题了?
# select 1
这种方式是很多 HA 下判断的标准,但是这个做不到万无一失。比如 set global innodb_thread_concurrency=3;
我用这个命令设置了 innodb 的并发线程数量为 3。
- 启动 4 个事务 ABCD。
- ABC 三个事务都执行
select sleep(100) from t;
,然后 D 事务执行select 1;
。 - 前三个事务会把所有的并发线程数量给占用,D 在执行查询的时候会被阻塞,但是执行
select 1;
却成功了。
这是因为 innodb_thread_concurrency 这个参数设置的是 InnoDB 的并发线程数量,而 select 1;
不需要访问 InnoDB 引擎,而查询需要访问 InnoDB。 所以如果以这种方式判断主库是否正常不太准确。
注意
innodb_thread_concurrency 默认为 0 表示不限制并发数量,但是实际中这个值必须设置,因为 CPU 核心数量有限所有请求全部进来,上下文切换成本太高了。
而且这个和连接数不是一个概念,连接数指的是能连接进来线程,而并发数指的是同一时间能够一起执行的线程。
线程进入锁等待的时候,并发线程的计数会减 1。因为进入锁等待的时候已经不需要再消耗 CPU 了,而且 MYSQL 也要防止死锁。
# 查表
在系统库里新建一个表 t 里面只放一行数据然后定期执行查询,用这个方法可以检测出并发线程过多造成的系统不可用。但是还是有问题。
比如说 binlog 所在的磁盘空间为 100% 了,所有的更新和新增的都会被阻塞,但是系统这个时候还是可以正常读取的。
# 更新
使用更新的可以放一个 timestamp 类型字段进去比如 t_modified,然后定时执行以下 update mysql.t set t_modified=now();
,而且需要在备库执行,但是备库的检测也是要写 binlog 的也会发回给主库,这样就会出现行冲突。
改造一下表 t,加一个 id 字段,id 是 server_id,因为每一台数据的 server_id 都不一样,这样就解决了上面这个问题,但是依旧是有一些问题,那就是 “判定慢”。
问题点在于,一旦有个库执行这个更新失败或者超时了,就代表不可用了,需要主备切换。这是个正常逻辑。
- 如果一个日志磁盘的 IO 利用率已经高达 100% 了,这个时候其实是已经不可用了,需要主备切换了。
- 但是 IO 利用率不代表这不可用,每个请求还是可以获取到 IO 资源的,而 update 需要的资源很少,可能在拿到资源的时候就能提交成功了,并且也返回给备库了。
- 然后 HA 发现主课还是在可用状态,然后就不做切换了。
所以还是会有问题。
# 内部统计
performance_schema.file_summary_by_event_name 记录了每次 IO 的请求时间。
- event_name='wait/io/file/innodb/innodb_log_file’:对应的是 redo log 的写入时间。
- event_name = "wait/io/file/sql/binlog":对应的是 binlog 的写入时间。
但是统计这些信息是需要性能消耗的,打开所有的 performance_schema 项,性能大概会下降 10%。所以建议只开启自己需要的。
开启 redo log 和 binlog 这两个统计信息,通过 MAX_TIMER 的值来判断数据是否出现了问题,如果说超过多少毫秒是异常,需要注意单位是皮秒,自定义设置的这个毫秒值需要乘 1000000000。发现有异常后,取到信息,把之前的统计记录清空。
清空之前的统计记录主要是为了让你的数据库从一个干净的状态开始重新计算这些指标。在数据库处理大量请求的过程中,这些指标会发生不断变化,可能会涉及到许多不同的事件和等待条件。如果不清空之前的统计记录,新找到的异常可能会被之前的数据淹没,这样就会很难确定问题出在哪里。
而且,如果你没有清空统计数据,就无法准确地测量并识别最近发生的性能问题,也就无法在新的问题出现时及时检测和解决。
# 上一章答案
如果使用 GTID 等位点的方案做读写分离,在对大表做 DDL 的时候会怎么样?
比如说主库执行了 10 分钟,那么 10 分钟后才能传到备库,备库执行也要 10 分钟,如果说这个时候请求到了备库,那么所有的查询都会超时,然后走向主库查询。所以建议在业务低峰期去做,确保主库能支撑所有的请求。