mysql-innodb笔记和Spring的那些事

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

一直觉得mysql怎么用用就行,深得公司DBA大神的影响,当各种细节需要处理的时候就会知道深入的太少,各种拙荆见肘,各种靠猜的武断决策,决心系统化通读下innodb的知识。

Mysql从使用到现在差不多跟工龄有的一比了,减掉前几年的Sql Server的时间,就算是对sql的范式稍微长进了那么一点,总是不甘心停留在知道,会这么用,至于为什么这么用,还是需要更加系统一点的知识,这里就做个笔记吧,顺便和平常项目开发举一反三一下。

因为不是DBA出生,很多内容都参考书上和网上的部分知识,并未花太多时间去验证,总觉得平常对mysql的知识接收的过于发散,难以系统化理解,心里永远一块搁不下的石头,最近花了很多时间去研究监控系统的来龙去脉,系统化的研究会带来很多知识的延伸,这些延伸会更加让整个系统架构更加具备可比性和可扩展性,在继续研究监控系统中,穿插再系统化研究下Mysql innodb的知识。

这篇博文的链接:http://www.j360.me/2016/10/27/mysql-innodb%E7%AC%94%E8%AE%B0%E5%8F%8A%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91%E6%83%B3%E6%B3%95%E4%B8%80%E4%BA%8C%E4%B8%89/

小小的一点抱怨

看到Mysql技术内幕这段话的时候很有感触

不系统的分析和未加验证的实验结果会产生误导,互联网更是加剧了结果的传播,每个人是受益者,也是受害者。包括我现在写的笔记,仅作为参考。

InnoDB

平常工作当中用的5.6版本,innodb1.2版本,所以只总结这方面吧

总结性一段话: InnoDB通过使用多版本并发控**(MVCC)** 来获得高并发性,并且实现了SQL标准的4种隔离级别,默认为REPEATABLE级别。同时,使用一种被称为next-keylocking的策略来避免幻读(phantom)

PS:从这里可以看出InnoDB在ACID中的I隔离性。

前面的一点概念知识

  1. 表的存储按照主键顺序存放,如果没有显示定义主键,则自动生成6字节的rowid作为主键。

    • 没有显示定义主键,则先去找第一个非空的唯一索引,第一个是指add index的顺序,而不是字段的顺序
    • 不满足上面条件,则是6字节的自动生成的指针
  2. innodb支持的作用

    • 最大支持64T
    • 事务
    • 行锁
    • 外键
    • XA,SAVEPOINTS
    • B-tree Index,Hash Index
mysql> show engines\G;
*************************** 1. row ***************************
      Engine: InnoDB
     Support: DEFAULT
     Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES
          XA: YES
  Savepoints: YES
*************************** 2. row ***************************
      Engine: MRG_MYISAM
     Support: YES
     Comment: Collection of identical MyISAM tables
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 3. row ***************************
      Engine: MEMORY
     Support: YES
     Comment: Hash based, stored in memory, useful for temporary tables
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 4. row ***************************
      Engine: BLACKHOLE
     Support: YES
     Comment: /dev/null storage engine (anything you write to it disappears)
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 5. row ***************************
      Engine: MyISAM
     Support: YES
     Comment: MyISAM storage engine
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 6. row ***************************
      Engine: CSV
     Support: YES
     Comment: CSV storage engine
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 7. row ***************************
      Engine: ARCHIVE
     Support: YES
     Comment: Archive storage engine
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 8. row ***************************
      Engine: PERFORMANCE_SCHEMA
     Support: YES
     Comment: Performance Schema
Transactions: NO
          XA: NO
  Savepoints: NO
*************************** 9. row ***************************
      Engine: FEDERATED
     Support: NO
     Comment: Federated MySQL storage engine
Transactions: NULL
          XA: NULL
  Savepoints: NULL
9 rows in set (0.00 sec)
  1. 索引

上面的概念里面说InnoDB支持3种索引:

- B+tree
- Hash索引
- 全文索引

这和我们在网上搜到的不一致嘛?! InnoDB生成的哈希索引是自适应的,DBA不能干预是否生成这货,字典查找时会使用哈希索引

另外,b+tree索引并不能定位到具体的行,而是只能定位到页,innodb把页的信息全部加载到内存中,在通过Page Directory 二分查找法定义到具体的行。切记

show engine innodb status\G;
\-------------------------------------
\INSERT BUFFER AND ADAPTIVE HASH INDEX
\-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 34679, node heap has 3 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 3 buffer(s)
Hash table size 34679, node heap has 4 buffer(s)
Hash table size 34679, node heap has 4 buffer(s)
Hash table size 34679, node heap has 3 buffer(s)
Hash table size 34679, node heap has 3 buffer(s)
Hash table size 34679, node heap has 3 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s

一些重要的概念:

  • 聚簇索引:顺序存放的主键,和他的所有的行的数据放一起,只能有一个
  • 辅助索引:就是非主键的其他定义的索引,可以有多个,这里并没有其他数据,但是会有一个关联的主键信息,便于关联到上面的信息

从这里可以看出来,通过辅助索引的查找的工作量起码是2倍了

看下索引吧

mysql> show index from user\G;
*************************** 1. row ***************************
        Table: user
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: uid
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: BTREE
      Comment:
Index_comment:
8 rows in set (0.00 sec)

索引的查询一二三

innoDB在使用查询时,一般都会去注意2个方面的问题

1. 如何建索引
2. 如何能用到索引 

如何建索引一般问题都比较容易,只需要注意这么几点

1.查询是不是需要用到
2.组合的查询建联合索引,看是否可以合并部分索引,向左原则
3.like查询使用x%时,向左原则
4.索引对字段长度的限制,需要满足条件
5.考虑索引的Cardinality值,也就是可取值的范围,比如男女/是否/1-0,就没必要索引了

如何能用到索引,是个比较麻烦的事情,不同情况innodb,特别是1.2版本之后的innodb根据配置也不一样,但是遵循的逻辑是可推的,多用explain才是王道

1.按主键的单查询索引是最快的
2.辅助索引的查询会分为ref和range,如果是范围查找innodb优化器会考虑全表扫描filesort,大概的范围是20%的阈值,这里可以强制使用索引 force index,而hint index的使用对innodb仅仅是参考
3.如果使用辅助索引,在可能的情况下使用覆盖索引 using index,即查询的字段在使用该索引字段的范围内
4.覆盖索引在进行部分统计 count(*)的时候可以被用到,补充下:凡是用到覆盖索引的地方,都可以用到索引
5.索引可以被使用到的关键字 = ,between,in,like x%
6.MRR优化是否开启,优化range,ref,eq_ref类型的查询,范围查询优化的思路就是将索引按照顺序的方式进行书签查找
7.ICP优化是否开启,索引查询的同时会部分按照where条件过滤,减少fetch的量级

如果按照单一索引进行查询,innodb在开启字典查询情况下会极大优化查询的性能,这里会用到innodb自己生成的自适应的哈希索引

  • Using where:表示优化器需要通过索引回表查询数据;
  • Using index:表示直接访问索引就足够获取到所需要的数据,不需要通过索引回表;
  • Using index condition:在5.6版本后加入的新特性(Index Condition Pushdown)ICP;

在这种情况如何定义索引,找到uid=1,2,3并且按创建时间排序取前3个,看下面的结果,取的是uid的索引+createtime_uid联合索引,想想为什么不是uid_createtime的联合索引

mysql> select * from userlink;
+----+-----+------------+------------+
| id | uid | createtime | updatetime |
+----+-----+------------+------------+
|  1 |   1 |          1 |          3 |
|  2 |   2 |          2 |          4 |
|  3 |   3 |          3 |          5 |
|  4 |   4 |          4 |          6 |
+----+-----+------------+------------+
4 rows in set (0.00 sec)

先看一个辅助索引用不上的场景
mysql> explain select * from userlink where createtime in (1,2)  order by uid desc limit 2;
+----+-------------+----------+------------+------+----------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table    | partitions | type | possible_keys  | key  | key_len | ref  | rows | filtered | Extra                       |
+----+-------------+----------+------------+------+----------------+------+---------+------+------+----------+-----------------------------+
|  1 | SIMPLE      | userlink | NULL       | ALL  | createtime_uid | NULL | NULL    | NULL |    4 |    50.00 | Using where; Using filesort |
+----+-------------+----------+------------+------+----------------+------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)


再看上述需求下的用例解释
mysql> explain select createtime from userlink  where  uid in (1,2) and createtime < 3 order by createtime desc limit 2;
+----+-------------+----------+------------+-------+--------------------+----------------+---------+------+------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys      | key            | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+----------+------------+-------+--------------------+----------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | userlink | NULL       | range | uid,createtime_uid | createtime_uid | 8       | NULL |    2 |    50.00 | Using where; Using index |
+----+-------------+----------+------------+-------+--------------------+----------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)


mysql> explain select * from userlink where uid in (1,2) and createtime < 3 order by createtime desc limit 2;
+----+-------------+----------+------------+-------+--------------------+----------------+---------+------+------+----------+-----------------------+
| id | select_type | table    | partitions | type  | possible_keys      | key            | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+----------+------------+-------+--------------------+----------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | userlink | NULL       | range | uid,createtime_uid | createtime_uid | 8       | NULL |    2 |    50.00 | Using index condition |
+----+-------------+----------+------------+-------+--------------------+----------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)


在修改createtime_uid联合索引,留下createtime的单一索引后的结果

mysql> explain select * from userlink where uid in (1,2) and createtime < 3 order by createtime desc limit 2;
+----+-------------+----------+------------+-------+----------------+------------+---------+------+------+----------+------------------------------------+
| id | select_type | table    | partitions | type  | possible_keys  | key        | key_len | ref  | rows | filtered | Extra                              |
+----+-------------+----------+------------+-------+----------------+------------+---------+------+------+----------+------------------------------------+
|  1 | SIMPLE      | userlink | NULL       | range | uid,createtime | createtime | 8       | NULL |    2 |    50.00 | Using index condition; Using where |
+----+-------------+----------+------------+-------+----------------+------------+---------+------+------+----------+------------------------------------+
1 row in set, 1 warning (0.00 sec)

结果证明联合索引在这里充分体现了联合索引的具体的存储方式为第一个索引会给第二个索引的顺序性查找增强了性能,icp在5.6也有所体现

全文索引

先把全文检索的语法列出来,建全文索引就是为了用

match against

select * from user where match(name) against ('j360')

innoDB1.2开始支持全文索引,简单总结下,参考资料的举例都是英文单词,可以想象到对中文的支持应该比较差,这里直接推荐ES了,在量级不大的海外英文主体市场可以尝试

相关值的依据有4个条件

1.word是否在文档中出现
2.word出现的次数
3.word在索引中的数量
4.多少个文档包含word

接着聊...

第二天...

innodb中锁是基于行锁的机制,MyISAM是表锁设计,提供的一致性的非锁定读、行级锁支持

行级锁没有额外的开销,并可以同时得到并发性和一致性。 《innoDB存储引擎一书》

innoDB有两种容易混淆的锁概念,lock 、latch

  • lock:针对事务,数据库内容,行锁,表锁,意向锁
  • latch:针对线程,内存数据结构,读写锁,互斥量,有点像java多线程的锁

这里只讲lock。

行级锁:

  • 共享锁S lock:允许事务读一行数据
  • 排它锁X lock:允许事务删除或更新一行数据

举例子:

对统一记录row 的兼容情况

  • 事务T1 read row100
  • 事务T2 read row100 √ 锁兼容 LockCompatible
  • 事务T3 update row100 × 锁不兼容

查看锁定情况:

  1. show engine innodb status
  2. select * from infomation_schema.innodb_trx
  3. select * from infomation_schema.innodb_locks
  4. select * from infomation_schema.innodb_lock_waits

一致性非锁定读:

读一行数据:如果正在执行X锁 update,delete,会去读快照数据,不会锁定(MVCC),读取的是undo (用来回滚)的数据

那么问题来了,多版本的控制意思就是会有多份快照数据,到底读的是哪一份数据?答案在这里 ↓

innodb的事务隔离级别,READ COMMITTED/REPEATABLE READ

  • READ COMMITTED:读取最新的一份,不满足事务间隔离性问题
  • REPEATABLE READ:读取事务开始的那一份,这个精确度更高,默认的innodb隔离级别

一致性锁定读:

这个平常基本上不敢用,但是有些情况下会用

select ... for update  加X锁
select ... lock in share mode 加S锁

如果使用一致性非锁定读,这和上面的锁定度不影响,因为会MVCC

自增

之前没有搞懂的一道亮丽的坑在这里填上。

自增的锁在提交完自动释放,而不是等待事务结束才释放。如果经过的事务都是不回滚的事务,则这里的自增是连续的,否则会不连续。

  • Auto-INC Locking:传统自增, Mysql 5.1.22之前,一种经过优化的表锁设计
  • innodb_autoinc_lock_mode,会有3个选择0/1(默认值)/2
  • 1的意思是,对于“simple inserts”(插入就已经知道插叙行数,insert,replace等)会使用互斥量对内存计数器进行累加,“buck inserts”(不确定的行数,)使用传统的自增,如果Auto-INC之后执行“simple inserts”,需要等待哦
  • 自增长的列必须是索引,且第一个列

SpringJdbcTemplate在执行insert操作切没有附加自增ID的值时,mysql会默认提供生成一个自增id,且Spring会通过prepareStatment回调拿到该自增的值,执行完该操作,如果成功,则可以将该id作为key返回,如果失败,该自增id丢弃,数据库自增id不连续。 留一个问题,spring是在什么时候拿到的这个自增id?翻一下源码看看。

这里是SpringJdbc中执行写操作的伪代码,Spring对JDBC的封装分为模板+回调,模板控制着事务中的操作,回调控制操作中的部分自定义内容,比如下面的:

public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
			throws DataAccessException {

		Assert.notNull(generatedKeyHolder, "KeyHolder must not be null");
		logger.debug("Executing SQL update and returning generated keys");

		return execute(psc, new PreparedStatementCallback() {
			@Override
			public Integer doInPreparedStatement(PreparedStatement ps) throws SQLException {
				// 在这里,执行了操作
				int rows = ps.executeUpdate();
				
				// 2) 在execute的函数中拿到了被包装的stmt的返回值
				List> generatedKeys = generatedKeyHolder.getKeyList();
				generatedKeys.clear();
				ResultSet keys = ps.getGeneratedKeys();
				if (keys != null) {
					try {
						RowMapperResultSetExtractor> rse =
								new RowMapperResultSetExtractor>(getColumnMapRowMapper(), 1);
						generatedKeys.addAll(rse.extractData(keys));
					}
					finally {
						JdbcUtils.closeResultSet(keys);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
				}
				return rows;
			}
		});
	}

如何调用上面的Spring的伪代码

public long insertAndGetKey(JdbcTemplate jdbcTemplate,final String sql, final Object... params) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            //这里标记处需要返回GENERATED_KEYS,是一个LinkedList,需要进一步解析,所以需要定义一个KeyHolder
                PreparedStatement pstmt = con.prepareStatement(sql, java.sql.Statement.RETURN_GENERATED_KEYS);
                for (int i = 0; i < params.length; i++) {
                    pstmt.setObject(i + 1, params[i]);
                }
                return pstmt;
            }
        }, keyHolder);
        
        // 4)最终拿到返回的主键id
        return keyHolder.getKey().longValue();
    }

GeneratedKeyHolder获取到最终的主键的Id


// 3) 这里KeyHolder的函数解析返回的值
public Number getKey() throws InvalidDataAccessApiUsageException, DataRetrievalFailureException {
		if (this.keyList.size() == 0) {
			return null;
		}
		if (this.keyList.size() > 1 || this.keyList.get(0).size() > 1) {
			throw new InvalidDataAccessApiUsageException(
					"The getKey method should only be used when a single key is returned.  " +
					"The current key entry contains multiple keys: " + this.keyList);
		}
		Iterator keyIter = this.keyList.get(0).values().iterator();
		if (keyIter.hasNext()) {
			Object key = keyIter.next();
			if (!(key instanceof Number)) {
				throw new DataRetrievalFailureException(
						"The generated key is not of a supported numeric type. " +
						"Unable to cast [" + (key != null ? key.getClass().getName() : null) +
						"] to [" + Number.class.getName() + "]");
			}
			return (Number) key;
		}
		else {
			throw new DataRetrievalFailureException("Unable to retrieve the generated key. " +
					"Check that the table has an identity column enabled.");
		}
	}
 
   

再往里面看,Spring最终回去调用连接池,比如Druid的conn

Connection con = DataSourceUtils.getConnection(getDataSource());
		Statement stmt = null;
		try {
			Connection conToUse = con;
			if (this.nativeJdbcExtractor != null &&
					this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
				conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
			}
			stmt = conToUse.createStatement();
			applyStatementSettings(stmt);
			Statement stmtToUse = stmt;
			if (this.nativeJdbcExtractor != null) {
				stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
			}
			//1)在这里把拿到的结果warpper包装下,返回给KeyHolder
			T result = action.doInStatement(stmtToUse);
			handleWarnings(stmt);
			return result;
		}

锁算法

  • a.Record Lock 行记录锁
  • b.Gap Lock 间隙锁,锁一个范围,不包含记录本上
  • c.Next-Key Lock:Gap Lock+Record Lord c=b+a 范围+记录本身,解决幻读问题

太复杂,还要好好归纳下

幻读 是连续两次读取到的内容不一致,这里和隔离性是息息相关的,两个不同事务间的隔离,在其他数据库中可能需要串行化的隔离级别才能达到,在READ COMMITED隔离级别中只有Record Lock,所以不具备隔离性的特征,存在幻读的可能。

究竟next key locking是如何解决的,用的就是上面的锁算法

脏读 脏读和幻读有点像,都是读取了别人的数据,违背隔离性的问题,但是有着本质上的区别,幻读最起码读取的内容是提交过的事务的数据,而脏读则是读取了未被提交的数据。这种问题在隔离级别为READ UNCOMMITED中会发生

不可重复读 也就是幻读,这种叫法更加符合隔离级别定义

后面3个问题在平常开发过程中很可能会碰到,即便在默认的隔离级别之下,所以这里作为开发人员要小心注意了

丢失更新 现在都是并发场景多,你把看到的内容经过计算后的提交内容,把我看到的内容经过计算处理后的提交内容覆盖了。啰嗦在什么地方,这里并没有违反什么规定,而是业务上需要注意看到这个读操作,也就是读和写之间存在了其他的操作,需要做的就是将读写串行化,对读加一致性读操作。【重要等级】

阻塞 等待别人的锁超时,报了超时异常,但是结果却执行了,需要注意。

死锁 死锁一律异常回滚,但是回滚的是谁需要探个究竟,FIFO是之前innodb的做法,现在也能够优化成通过位图来计算事务大小回滚事务小的这个,谢谢oracle。

死锁在位图中的表达方式就是一个回路,我调了你的资源锁你也调了我的资源锁,互相持有对方需要的资源。

把这些搞清楚了,再看看事务吧。

PS;innodb没有锁升级的概念,其他数据库有

事务

等待完成... 事务这节笔记是看的最激动的一节,配合JTA+Spring对于事务的传播方式可以从开发的角度理解这些配置的来龙去脉,再往上深入到前面的章节,对知识做一个深入,也算是对平常开发过程中各种用法理论上的交代。

innodb的事务

innodb是能够实现ACID特性的数据库引擎,这些都和事务息息相关,事务在Spring的传播机制谈到了一些配置,具体用到innodb时又是怎么关联的,先列出事务的种类:

  1. 扁平化事务
  2. 保存点事务
  3. 链式事务
  4. 嵌套事务 innodb不支持
  5. 分布式事务

根据各自的名称也能猜得到意思,在spring的传播机制中特地提到了大小事务的概念,看来这里是不支持了,但是spring中的嵌套事务是否是这里提到的嵌套事务呢,接着验证。在某些情况下保存点事务通过某些方法是可以变种成嵌套事务的。这就是Savepoint机制了。它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager) Spring的定义是这么解释的: PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的“子事务”(这个好像有点不对). 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

扁平化事务是使用中几乎是绝对使用的范畴,意思就是全部是了,关于保存点事务就是回滚到一个保存点上,不至于回滚整个事务,这里在批处理的时候用途很大。链式事务会带上上一个事务的部分信息,上一个事务是一个commit的事务,但是后者可以看到上一个事务的结果。具体和Spring的事务有着何种关系,在Spring中怎么配置,见下面的Spring 配置。

Spring对于事务的配置

Spring在JDBC中定义了实际操作中的异常,基于Driver的异常少的可怜,根据mysql返回给client的错误代码可以得到具体的异常。

JDBC异常体系

  • BatchUpdateException
  • DataTruncation
  • SQLException
  • SQLWarning

Spring异常体系太多了,只能看源码了,看DataAccessException的子类。

先看看Spring对于事务的定义都有哪些选项,具体的代码可以通过DataSourceTransactionManager来查看,在这里可以通过事务的aop实现简单的读写分离,也就是通过方法名做为读写分离技术的依据key,比如通过前缀匹配、正则等。这里也有一个前提,相同线程下的同一个事务请求需要走同一个db,具体走哪一个需要指定读还是写,有读有写则一并写到写库。


		
			
				
					
						
							
						
					
					
						
							
						
						
							
							
								
								
								
								
								
								
								
							
						
					
					
						
							
						
						
						
							
								
								
								
								
								
							
						
					
					
						
							
						
					
					
					
						
							
						
					
					
						
						
							
						
					
					
						
							
						
					
				
			
		
	

只读事务在这里体现在一致性读和非一致性读的方式。 InnoDB默认的read only有两种方式:

  • The transaction is started with the START TRANSACTION READ ONLY statement.
  • The autocommit setting is turned on(打开)

详细说明,来自于官方的解释: https://dev.mysql.com/doc/refman/5.7/en/innodb-performance-ro-txn.html

  • InnoDB can avoid the overhead associated with setting up the transaction ID (TRX_ID field) for transactions that are known to be read-only. A transaction ID is only needed for a transaction that might perform write operations or locking reads such as SELECT ... FOR UPDATE. Eliminating(排除) unnecessary transaction IDs(不必要的transaction id) reduces(降低) the size of internal data structures that are consulted(商量,顾及) each time a query or DML(数据操纵语言) statement constructs a read view. Currently, InnoDB detects(发现,发觉) read-only transactions when: The transaction is started with the START TRANSACTION READ ONLY statement. In this case, attempting to make changes to the database (for InnoDB, MyISAM, or other types of tables) causes an error, and the transaction continues in read-only state:
ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction.

You can still make changes to session-specific temporary(临时的) tables in a read-only transaction, or issue locking queries for them, because those changes and locks are not visible to any other transaction.

  • The autocommit setting is turned on(打开), so that the transaction is guaranteed to be a single statement, and the single statement making up the transaction is a “non-locking” SELECT statement. That is, a SELECT that does not use a FOR UPDATE or LOCK IN SHARED MODE clause.

  • The transaction is started without the READ ONLY option, but no updates or statements that explicitly lock rows have been executed yet. Until updates or explicit locks are required, a transaction stays in read-only mode.

关于Savepoint机制在上面的配置中没有看到,嵌套的配置是不是还需要再验证,但是可以通过编程创建基于Savepoint的嵌套事务。

txTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
BigDecimal transferAmount = new BigDecimal("20000");
try
{
withdraw("WITHDRAW_ACOUNT_ID",transferAmount);
Object savePointBeforeDeposit = txStatus.createSavepoint();
try
{
deposit("MAIN_ACOUNT_ID",transferAmount);
}
catch(DepositException ex)
{
logger.warn("rollback to savepoint for main acount transfer failure",ex);
txStatus.rollbackToSavepoint(savePointBeforeDeposit);
deposit("SECONDARY_ACOUNT_ID", transferAmount);
}
finally
{
txStatus.releaseSavepoint(savePointBeforeDeposit);
}
}
catch(TransferException e)
{
logger.warn("failed to complete transfer operation!",e);
txStatus.setRollbackOnly();
}
}});

那些不了了之的未知

  1. 开门见山,innodb不支持嵌套事务,所以在平常使用spring的时候很多人提起spring的事务传播机制并且讨论各种情况的时候,如果用innodb是提供不了支持了,不过可以使用savepoint机制进行改造

  2. xa事务需要使用的驱动也必须支持xa,并且链接池是支持xa的,java在使用时需要依赖一些第三方的类库进行操作,比如atomikos,jotm等

  3. 常说的乐观锁悲观说:这个只是业务上的叫法,一般for update悲观锁,意思就是假设一定不一致,就是用锁,另一个方式是version的方式,select version from xxx where id = x,更新时使用update xx where version=xxx and id = x.假设一致,这种情况下会存在碰撞失败一致重试的可能,意思和java的乐观锁cas概念有点相似,可以看看 AQS的思路,类AbstractQueuedSynchronizer。

AQS -> 狂魔兄的博客:https://my.oschina.net/pingpangkuangmo/blog/679636

  1. 连接池如何去设置:配置初始值,再联系DBA获得线上数据库支持的连接数,计算最大连接数。连接有效性检查,只在连接空闲检测时执行,不在borrow和return时执行,最好是直接使用数据的Ping方案,不要配置检查SQL。 from 江南白衣的博客
  2. 分表分库:一般都尝试在java客户端框架处理(shard-jdbc,tddl),减少两者之间过多的跳转,mycat毕竟中间加了一个环节。

项目中碰到的问题

  1. 覆盖索引:index
  2. 分页问题:limit
  3. 关联问题:join
  4. 显式事务:声明式 or 编程式
  5. 读写分离:声明式 or sqlparse
  6. 低并发统计:count+1,高并发redis回写
  7. count(*) count(1) count(非主键)
  8. 多组order order and union
  9. 多层聚合 join group by
  10. 批处理 (多线程批处理,Spring batch,data load)批处理导入尽量减少事务的提交,也就是不在循环中提交事务

参考

  • mysql技术内幕-innodb
  • spring实战
  • spring.io

没有贴的细节:Spring读取配置后如何去针对各种事务情况处理的源码

PS:网上贴的各种mysql注意事项,经过一段时间的学习,大体上都是没有问题的,只是在特殊地方有着不同的处理方式,特别是到了高级语言客户端中如何去处理便又是另一个世界的问题了,但最终还是逃不掉数据库引擎的一片天。

这篇博文的链接:http://www.j360.me/2016/10/27/mysql-innodb%E7%AC%94%E8%AE%B0%E5%8F%8A%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91%E6%83%B3%E6%B3%95%E4%B8%80%E4%BA%8C%E4%B8%89/

转载于:https://my.oschina.net/smartsales/blog/785314

你可能感兴趣的:(java,python,系统架构)