mysql使用心得----进阶

进阶讨论

alter table的并发性讨论

现象:

如果有事务正在修改某张表T,此时执行alter table T语句会挂住。

另外,将alter table放在一个事务中,模拟大表的长时操作,发现此时查询、删除这张表的记录均无问题。
是否说明alter table并不锁表呢?

我们先来看看alter table的原理,mysql5.7的手册上有一段描述:

ALTER TABLE operations are processed using one of the following algorithms:

COPY: Operations are performed on a copy of the original table, and table data is copied from the original table to the new table row by row. Concurrent DML is not permitted.

INPLACE: Operations avoid copying table data but may rebuild the table in place. An exclusive metadata lock on the table may be taken briefly during preparation and execution phases of the operation. Typically, concurrent DML is supported.

The ALGORITHM clause is optional. If the ALGORITHM clause is omitted, MySQL uses ALGORITHM=INPLACE for storage engines and ALTER TABLE clauses that support it. Otherwise, ALGORITHM=COPY is used.

ALTER TABLE operations that use the COPY algorithm wait for other operations that are modifying the table to complete. After alterations are applied to the table copy, data is copied over, the original table is deleted, and the table copy is renamed to the name of the original table

alter table有两种算法,一种是就地(inplace)修改或重建表,一种是把数据复制到新表,最后把新表rename回老表(rename动作是原子的,而且非常快)。algorithm的语法像这样:

alter table t_test drop column desp1, algorithm=copy;

algorithm=copy时只允许查询,不允许DML语句,algorithm=inplace则支持并行的DML。默认使用的是inplace,也就是alter table时允许并行DML,并不锁表。

algorithm=copy时不允许DML的原因,应该是怕丢失了原表最新的改动,所以干脆通过锁表禁止了增删改动作。这种做法与pt-online-schema-change工具的区别在于,后者通过触发器可以同步原表最新的改动到新表,因此后者允许并行DML。另外,algorithm=copy也需要更多的空间来存放备份数据。

alter table时也可以通过lock语法指定并发级别,有如下四个级别:

LOCK = DEFAULT

Maximum level of concurrency for the given ALGORITHM clause (if any) and ALTER TABLE operation: Permit concurrent reads and writes if supported. If not, permit concurrent reads if supported. If not, enforce exclusive access.

LOCK = NONE

If supported, permit concurrent reads and writes. Otherwise, an error occurs.

LOCK = SHARED

If supported, permit concurrent reads but block writes. Writes are blocked even if concurrent writes are supported by the storage engine for the given ALGORITHM clause (if any) and ALTER TABLE operation. If concurrent reads are not supported, an error occurs.

LOCK = EXCLUSIVE

Enforce exclusive access. This is done even if concurrent reads/writes are supported by the storage engine for the given ALGORITHM clause (if any) and ALTER TABLE operation.

看起来,LOCK = DEFAULT和LOCK = NONE的并发性是最好的。

我们再看看常见的alter table的场景:

  • 增删列
  • 修改列的类型
  • 增加索引

其中:

增删列可使用ALGORITHM=INPLACE,因此允许并行读写。但是是一个代价高昂的操作,意味着表的数据基本上要被重新组织,跟重建表差不多了。

修改列的类型只能使用ALGORITHM=COPY,且表数据要被重新组织,因此不允许并行的读写操作。

增加索引使用ALGORITHM=INPLACE,因此不影响并行的读写操作,其耗时仅取决于表的数据量。

其它的alter table场景的讨论参看这里。

结论:

大表表结构调整,比如常见的增删列、修改列类型、增删索引等,在mysql5.6之前是直接锁表的,那时只能靠pt-online-schema-change等工具解决表可用性问题。到了5.7 mysql支持online DDL,部分操作可不用锁表了。不过,修改列类型依然会暴力锁表,建议还用pt-online-schema-change工具替代。增删列、增删索引则不会锁表,因此支持并行的读写访问,但这两类操作,尤其是前者,代价高昂,意味着底层表数据的重新组织,基本上等价于表重建了,建议放到业务低谷时段执行。同时,考虑到pt-online-schema-change工具能对主从同步延时做出自适应调整,也可以对所有的大表表结构调整都使用pt工具。

pt-online-schema-change工具说明参见文1和文2:

select into的替代写法

mysql不支持select into的写法,该写法可以复制一张表的结构和数据到一张新表。替代写法是:

create table new_table_name (Select * from old_table_name);

比如完全复制t_Machines的结构和数据到t_Machines_test,t_Machines_test事先并不存在:

create table t_Machines_test (Select * from t_Machines);

事务下的锁

做个测试即可知道。建一张临时表:

create table t_test(
id int,
desp varchar(255) null,
primary key (id)
);

session1,事务这么写,留15s的停顿:

START TRANSACTION;
update t_test set desp='blade1' where id=1;
update t_test set desp='sword1' where id=2;
insert into t_test values(3, 'balong');
select SLEEP(15);
COMMIT;

session2,执行查询、更新和插入:

select * from t_test;
update t_test set desp='blade3' WHERE id=1;
insert into t_test values(4, 'dao'); -- insert into t_test values(3, 'dao');

session3,另起一个事务:

START TRANSACTION;
update t_test set desp='blade3' where id=1;
COMMIT;

session2 现象:

  • 查询依然可以读出全部数据,只是数据还是老的;
  • 更新操作会挂住,直到session1事务完成才会执行;
  • insert操作如与事务中的insert无主键冲突,立刻执行成功;否则,挂起,直到事务完成,失败

session3 现象:

  • 事务会挂住,直到session1事务完成才会继续执行

可见,mysql的事务在innoDB引擎下,会尽可能使用行锁,而非表锁,且该行锁会一直保持到事务结束。至于为何事务未提交,select依然能读出老的数据,我理解应该是锁机制之外,事务的MVCC(multi-version consistency control)机制在起作用,mysql不希望出现脏读,也不希望查询语句就此挂住,就让select读出历史快照数据,这被称为快照读。

还有,若要确保读出来的是最新的数据,可使用locking reads或for update:

select * from t_test lock in share mode;
select * from t_test for update;

那么,在事务执行期间,这条查询语句会挂住,直到session1事务执行完毕。这时的行为,就跟我以前测试sybase时一致了。

所以,认为“mysql的事务锁像mongodb那样是乐观锁”的观点是不对的,可以认为仅仅是mysql对普通select语句做了一些基于MVCC的优化罢了。

select for update和lock in share mode

关于locking reads,手册上这么介绍的:

Sets a shared mode lock on any rows that are read. Other sessions can read the rows, but cannot modify them until your transaction commits. If any of these rows were changed by another transaction that has not yet committed, your query waits until that transaction ends and then uses the latest values.

参考文档。

select for update是一个排他锁,如果结合事务使用,相当于对一条记录持续加排他锁。如果这条记录不存在,就不会加锁。

select for update比lock in share mode独占性更强,相当于悲观写,后者是悲观读。

这是网上找的一段两者的区别说明:

SELECT ... LOCK IN SHARE MODE走的是IS锁(意向共享锁),即在符合条件的rows上都加了共享锁,这样的话, 其他session可以读取这些记录,也可以继续添加IS锁,但是无法修改这些记录直到你这个加锁的session执行完成(否则直接锁等待超时)。  SELECT ... FOR UPDATE 走的是IX锁(意向排它锁),即在符合条件的rows上都加了排它锁, 其他session也就无法在这些记录上添加任何的S锁或X锁。如果不存在一致性非锁定读的话,那么其他session是无法读取和修改这些记录的,但是innodb有非锁定读(快照读并不需要加锁),for update之后并不会阻塞其他session的快照读取操作,除了select ...lock in share mode和select ... for update这种显式加锁的查询操作。  
通过对比,发现for update的加锁方式无非是比lock in share mode的方式多阻塞了select...lock in share mode的读操作,并不会阻塞快照读。 

事务隔离级别查询

四种事务隔离级别:

READ_UNCOMMITTED(读未提交,即脏读)

READ_COMMITTED(读已提交,只能读其他事务已提交的数据,但不能重复读)

REPEATABLE_READ(可重复读)

SERIALIZABLE(串行读,读的时候,其他写操作会挂住,读操作可继续,效率较低) 

隔离强度越来越高,效率应该也越来越低。

mysql里可用如下命令查询事务隔离级别:

SELECT @@tx_isolation
SELECT @@transaction_isolation;

参考这里。

对json的支持

mariadb 10.0.X版本可利用dynamic column来支持json的读写。

建表:

create table t_test1 (
  item_name varchar(32) primary key, -- A common attribute for all items
  dynamic_cols  blob  -- Dynamic columns will be stored here
);

插入记录:

INSERT INTO t_test1 VALUES ('MariaDB T-shirt', COLUMN_CREATE('color', 'blue', 'size', 170));
INSERT INTO t_test1 VALUES ('Thinkpad Laptop', COLUMN_CREATE('color', 'black', 'price', 500));

查询:

-- 查询列值
SELECT item_name, COLUMN_GET(dynamic_cols, 'color' as char) AS color FROM t_test1;

-- 查询所有列
SELECT item_name, column_list(dynamic_cols) FROM t_test1;

-- 查询json样式的值
SELECT item_name, COLUMN_JSON(dynamic_cols) FROM t_test1;

支持了json格式之后,mysql已经初步具备一些mongodb的特征。

主键和索引

primary key: 主键聚集索引,一个表只有一个PRIMARY KEY

key或index: 普通索引

Unique Index:只是属于Index中的一种而已,建立了Unique Index表示此列数据不可重复 。注意:primary key天然是unique key

自然键 or 代理键

参考这里。

自增主键的优缺点

优点:

(1)数据库自动编号,速度快,而且是增量增长,按顺序存放,对于检索非常有利;

(2)数字型,占用空间小,易排序,在程序中传递也方便;

(3)如果通过非系统增加记录时,可以不用指定该字段,不用担心主键重复问题。

说完优点顺便说说它的缺点,其实它的缺点也就是来自其优点。

缺点:

(1)因为自动增长,在手动要插入指定ID的记录时会显得麻烦,尤其是当系统与其它系统集成时,需要数据导入时,很难保证原系统的ID不发生主键冲突(前提是老系统也是数字型的)。特别是在新系统上线时,新旧系统并行存在,并且是异库异构的数据库的情况下,需要双向同步时,自增主键将是你的噩梦;

(2)在系统集成或割接时,如果新旧系统主键不同是数字型就会导致修改主键数据类型,这也会导致其它有外键关联的表的修改,后果同样很严重;

(3)若系统也是数字型的,在导入时,为了区分新老数据,可能想在老数据主键前统一加一个字符标识(例如“o”,old)来表示这是老数据,那么自动增长的数字型又面临一个挑战。

批量插入或更新

要求:记录不存在则insert,如果存在则update。

解决方法:

1、使用ON DUPLICATE KEY UPDATE

INSERT INTO table (a,b,c) VALUES (1,2,3)

         ON DUPLICATE KEY UPDATE c=c+1;

2、使用replace into

两者的效率对比如何?

并发情况下的结论:

带自增ID,使用replace 容易死锁,不要使用replace into。两者效率接近。

当数据量达到100W时,单纯修改,少量慢日志,同时的改查,慢日志明显。两者性能接近。

参考这里。

replace into和insert on duplicate的区别

注意,除非表有一个PRIMARY KEY或UNIQUE索引,否则,使用一个REPLACE语句没有意义。该语句会与INSERT相同,因为没有索引被用于确定是否新行复制了其它的行 。

I would recommend using INSERT...ON DUPLICATE KEY UPDATE.

If you use INSERT IGNORE, then the row won't actually be inserted if it results in a duplicate key. But the statement won't generate an error. It generates a warning instead. These cases include:

Inserting a duplicate key in columns with PRIMARY KEY or UNIQUE constraints.
Inserting a NULL into a column with a NOT NULL constraint.
Inserting a row to a partitioned table, but the values you insert don't map to a partition.

If you use REPLACE, MySQL actually does a DELETE followed by an INSERT internally, which has some unexpected side effects:

A new auto-increment ID is allocated.
Dependent rows with foreign keys may be deleted (if you use cascading foreign keys) or else prevent the REPLACE.
Triggers that fire on DELETE are executed unnecessarily.
Side effects are propagated to replicas too.

顺带说一下,insert on duplicate的原理是先insert,如果出错,就尝试update。insert和update肯定是原子的,但insert on duplicate不一定是原子的,因为insert和update之间还有一段很短的时间。

replace into的陷阱

如果同时有自增列和unique键,会使用unique键作为唯一判断,更新掉自增列的值。ON DUPLICATE KEY UPDATE 就无该问题

自动类型转换

假设t_myhuaweiimuser_log的id为整型自增列,则这样写也是可以的:

select * from t_myhuaweiimuser_log where id="21";

mybatis也利用了这个特性避免sql注入。

参见这里。

你可能感兴趣的:(mysql,数据库)