# 存储引擎
- MySQL 常见的几种存储引擎:
- MyISAM :一个传统引擎。不支持外键、事务、行级锁,只支持表级锁。
- InnoDB :从 MySQL 5.5 开始成为默认引擎,支持外键、事务、行级锁。
- MySQL 同一个数据库中的不同数据表,可采用不同的存储引擎。
# 事务
- InnoDB 引擎支持事务,可以将连续执行的多个 SQL 命令声明为一个事务:
BEGIN; -- 开始一个事务,也可写作 START TRANSACTION ... ROLLBACK; -- 回滚到 BEGIN 时的状态 ... SAVEPOINT p1; -- 声明一个回滚点 ... ROLLBACK TO p1; -- 回滚到指定位置 ... COMMIT; -- 提交当前事务
- 相关命令:
SELECT * FROM information_schema.innodb_trx; -- 查询当前的所有事务 SET AUTOCOMMIT=0` -- 取消自动提交事务。否则默认每执行一条 SQL 命令就自动作为一个事务提交
# 数据库锁
关于表级锁的命令:
LOCK TABLES [<tb> <lock_type>],... -- 给指定表加锁,使当前客户端会话获得表锁,例如 LOCK TABLES tb1 WRITE, tb2 READ; UNLOCK TABLES -- 释放当前会话的所有表锁 FLUSH TABLES -- 强制关闭所有被打开的表,并将写缓存中的数据写入磁盘 tb,... -- 只处理指定的表 WITH READ LOCK -- flush 之后加上全局只读锁。这是全局锁,因此不需要获得 lock tables 的表锁 SHOW STATUS LIKE 'Table_locks%'; -- 统计获得表锁的耗时
- 客户端执行
LOCK TABLES
或START TRANSACTION
时,都会隐式地释放已获得的表锁。- 客户端断开会话时,服务器会释放其获得的表锁。
- 客户端执行
关于行级锁的命令:
SELECT * FROM ... WHERE ... LOCK IN SHARE MODE -- 给查询到的所有数据行加行级共享锁 SELECT * FROM ... WHERE ... FOR UPDATE -- 加行级排它锁 SHOW STATUS LIKE 'InnoDB_row_lock%'; -- 统计获得行锁的耗时 SELECT * FROM performance_schema.data_locks; -- 显示所有获取行锁的请求,包括已经获得的、等待获得的 SELECT * FROM performance_schema.data_lock_waits; -- 显示 data_lock_waits 中的哪些请求在被哪些请求阻塞
InnoDB 的行锁是通过给索引中的索引键加锁来实现的。
- 如果不使用索引进行查询,则行锁不起作用,只能使用表锁。
- 如果针对不同的数据行加行锁,却使用相同的索引键,则也会发生锁冲突。
InnoDB 在申请行锁时,会先隐式地申请该表的意向锁(intention lock),类型也为共享或排它。
- 意向锁是一种特殊的表锁,表示意图对该表加行锁。
- 意向锁不与意向锁冲突。
- 意向共享锁与表级共享锁不冲突,其它意向锁与表级锁都冲突。
- 意向锁由 InnoDB 自动获得、释放,客户端不能控制。
- 使用意向锁,InnoDB 能更快地发现表级锁是否冲突。
- 例:
- 事务 A 执行
SELECT * FROM tb1 WHERE id=1 FOR UPDATE;
,先请求获得对 tb1 表的意向排它锁,成功之后再请求获得对 id=1 的数据行的排它锁。 - 事务 B 执行
SELECT * FROM tb1 WHERE id=2 FOR UPDATE;
,与事务 A 不冲突,能够获得意向排它锁、行级排它锁。 - 事务 C 执行
LOCK TABLES tb1 READ;
,请求获得表级只读锁,但 tb1 表已有意向排它锁,因此阻塞等待。
- 事务 A 执行
- 意向锁是一种特殊的表锁,表示意图对该表加行锁。
InnoDB 提供的行锁属于悲观锁,用户可以自己编程实现乐观锁。如下:
SELECT name FROM tb1 WHERE id = 1; -- 先查询下修改之前的值,这里假设此时 name 的值为 'one' UPDATE tb1 SET name='two' WHERE id=1 AND name='one'; -- 执行之后,根据返回值判断是否修改成功
- 可以根据 timestap 等字段来判断数据是否被修改。
相关配置:
innodb_lock_wait_timeout = 50 # 事务请求获取 row lock 时,等待的超时时间,默认为 50s 。超时则报错:Lock wait timeout exceeded innodb_print_all_deadlocks = OFF # 每次发生死锁时,是否记录日志,默认为 OFF 。执行 SHOW ENGINE INNODB STATUS 即可查看死锁日志 innodb_rollback_on_timeout = OFF # innodb_lock_wait_timeout 时,是否回滚整个事务。默认为 OFF ,只回滚最后一条语句,可能破坏事务原子性
# DDL
MySQL 修改数据表的 DDL 语句,有多种实现算法:
- copy
- :拷贝一个新表,步骤如下:
- 新建一个新表。
- 锁定原表,禁止 DML ,允许 DQL 。
- 将原表的数据逐行拷贝到新表。
- 删除原表,将新表 RENAME 为原表。
- 缺点:
- 需要两倍的存储空间。
- 会阻塞 DML 操作,可能中断客户端业务。
- :拷贝一个新表,步骤如下:
- inplace
- :在原表中进行修改,分为两种方式:
- rebuild-table :需要重建数据表,因此会消耗大量时间、磁盘 IO 。
- not-rebuild-table :默认尽量采用这种。
- 缺点:
- 会锁定原表,阻塞 DML 操作。
- 于 MySQL v5.5 加入。
- :在原表中进行修改,分为两种方式:
- instant
- :于 MySQL v8.0 加入,只支持少量 DDL 操作。
- copy
Online DDL :用于在不中断客户端业务的情况下执行 DDL 操作。
- MySQL v5.6 给 InnoDB 引擎增加了 Online DDL 功能,并默认启用。
- 可以主动指定 Online DDL 策略,如下:
ALTER TABLE tbl_name ADD PRIMARY KEY (column), ALGORITHM=INPLACE, LOCK=NONE;
- ALGORITHM 表示 DDL 算法,可取值:
- DAFAULT :自动选择最高效的算法。
- COPY
- INPLACE
- LOCK 表示加锁类型:
- DAFAULT :尽量选择最低的锁,从而允许并发 DML 。
- NONE :不加锁。
- SHARED :共享锁。
- EXCLUSIVE :排它锁。
- 如果指定的策略不支持当前的 DDL 操作,则会报错:
ALTER TABLE tb1 RENAME TO tb2, LOCK=NONE; > 1845 - LOCK=NONE/SHARED is not supported for this operation. Try LOCK=EXCLUSIVE.
- ALGORITHM 表示 DDL 算法,可取值:
# innodb_data
- InnoDB 引擎的大部分数据都保存在系统表空间(System Tablespace)中,包括:
- 每个表的数据页、索引
- change buffer
- data dictionary
- doublewrite buffer
- undo log
- 系统表空间在磁盘保存为一个或多个文件。
- 早期的 InnoDB 引擎,将系统表空间都保存在
$datadir/ibdata1
文件中。- InnoDB 引擎不会减小 ibdata1 文件的体积。即使 DELETE 了一些数据,也不会释放磁盘空间,而是留给未来使用。
- 可以通过 mysqldump 重建数据库,从而减小 ibdata1 文件。
- MySQL v5.6.6 开始,默认设置了
innodb_file_per_table=ON
,将每个数据表的数据页、索引分别保存到$datadir/<db_name>/<table_name>.ibd
文件中,而 ibdata1 文件用于保存其它数据。- 这样 DROP TABLE 时会自动释放磁盘空间。
- 早期的 InnoDB 引擎,将系统表空间都保存在
# 表碎片
InnoDB 表以 B+ tree 结构存储数据。删除一行数据时,并不会释放存储空间,而是标记为 free ,留待以后写入新数据。这些 free 空间称为碎片(fragment)。
- 执行 DELETE、UPDATE 操作可能产生碎片。
- 如果碎片空间比新数据行体积小,则一直不会用于写入新数据。
- 一般按
data_free / (data_length + index_length)
计算碎片率。 - 如果碎片率长时间较高,建议手动清理碎片,从而减少占用的存储空间、减少读取表的耗时。
- 每个数据表占用的磁盘空间等于:全部数据行 + 索引 + 碎片空间。
相关命令:
-- 显示所有数据表的状态,其中 Data_free 表示 free 数据占用的存储空间,单位 bytes SHOW TABLE STATUS; -- 统计所有表的数据体积、索引体积、碎片体积、碎片率,只显示大于 10% 的表 SELECT TABLE_SCHEMA AS `database`, TABLE_NAME AS `table`, ENGINE AS engine, DATA_LENGTH/1024/1024 AS data_length, INDEX_LENGTH/1024/1024 AS index_length, DATA_FREE/1024/1024 AS data_free, DATA_FREE/(DATA_LENGTH+INDEX_LENGTH) AS free_rate FROM information_schema.tables WHERE DATA_FREE > 10*1024*1024; -- MyISAM 表可用该命令清理碎片,这会暂时锁定表 OPTIMIZE TABLE <tb>...; -- InnoDB 表可用该命令清理碎片。这会以 Online DDL 方式拷贝出一个无碎片的新表,然后重命名为原表 ALTER TABLE <tb> engine=InnoDB; ANALYZE TABLE <tb>;
mysqlcheck 命令的用法:
mysqlcheck -h <host> -P <port> -u <user> -p -o # --optimize ,优化表 <db> <tb> # 指定数据库中的数据表 --databases <db>... # 指定多个数据库的数据表 --all-databases # 指定所有数据库