你要如何衡量你的人生

坚持,努力,让好事发生

mac系统的一些总结

(1) mac新建用户

() Menu -> System Preferences -> Users & Groups
点 + 号,新建用户

(2) mac修改用户名及个人目录

先切换另一个管理员用户 修改目录名 (/Users/weikeqin)
再修改 Home directory(个人目录)

修改用户名
  mac修改用户名及个人目录稍微有点麻烦。

  1. 创建一个新用户,授予管理员权限。
  2. 退出登录,使用新用户登录。
  3. 在Finder中打开 Go to Floder,输入 /Users ,点右键rename(重命名),然后把 老的个人目录名 修改成 新的个人目录名。 (这一步很重要)
  4. () Menu -> System Preferences -> Users & Groups , 按 锁形图标,输入管理员名称和密码。
  5. 然后在左侧用户列表,选中要修改的用户,右键 -> Advanced Options
  6. 然后把 Account name 改成自己想要改的用户名。
  7. 把 Home directory 选成 第3步改后的 新目录名。 (这一步很重要)
  8. 重启电脑。

第3步对应下图 如果第1 2步有问题,第3步里是没有rename这个选项的
重命名个人目录名

第5步对应下图
用户和组-高级选项

第7步对应下图
修改个人目录名

更改 macOS 用户帐户和个人文件夹的名称

(2.1) 修改计算机名

修改计算机名

() Menu -> System Preferences -> Sharing 然后修改 computer name

阅读全文 »

索引类似于字典的目录,目的是为了提高查询效率。

如词典的目录、图书的目录等。它们的原理都是一样的,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是我们总是通过同一种查找方式来锁定数据。

(1) 常见的索引模型

索引的出现是为了提高查询效率,但是实现索引的方式却有很多种,可以用于提高读写效率的数据结构很多,介绍三种常见、也比较简单的数据结构,它们分别是哈希表、有序数组和搜索树。

(1.1) hash索引

哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的键即 key,就可以找到其对应的值即 Value
哈希表这种结构适用于只有等值查询的场景

优点:等值查询快
缺点:不适合范围查询

(1.2) 有序数组

有序数组在等值查询和范围查询场景中的性能就都非常优秀

在需要更新数据的时候就麻烦了,你往中间插入一个记录就必须得挪动后面所有的记录,成本太高。

有序数组索引只适用于静态存储引擎

(1.3) 二叉搜索树

查询的时间复杂度是 O(log(N))
更新的时间复杂度是 O(log(N))

一棵 100 万节点的平衡二叉树,树高 20。一次查询可能需要访问 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间,这个查询可真够慢的。

(1.4) N叉搜索树

为了让一个查询尽量少地读磁盘,就必须让查询过程访问尽量少的数据块。那么,我们就不应该使用二叉树,而是要使用“N 叉”树。这里,“N 叉”树中的“N”取决于数据块的大小。

以 InnoDB 的一个整数字段索引为例,这个 N 差不多是 1200。(MySQL页大小是16K,主键用bigint类型8字节 指针6字节 16000/(8+6)=1142 ≈ 1200)
这棵树高是 4 的时候,就可以存 1200 的 3 次方个值,这已经 17 亿了。(假设只存一个字段bigint类型)
考虑到树根的数据块总是在内存中的,一个 10 亿行的表上一个整数字段的主键索引,查找一个值最多只需要访问 3 次磁盘。

阅读全文 »

 提到事务,肯定不陌生。最经典的例子就是转账,张三要给李四转 100 块钱。

 转账时要从张三的账户扣100块钱,然后给李四的账户加100块钱,张三账户扣钱李四账户加钱必须保证是一体的。

 简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。

在 MySQL 中,事务支持是在引擎层实现的。

阅读全文 »

字节码增强技术探索

[wkq@VM_77_25_centos java_test]$  vi HelloWorld.java
[wkq@VM_77_25_centos java_test]$
[wkq@VM_77_25_centos java_test]$ javac HelloWorld.java
[wkq@VM_77_25_centos java_test]$
[wkq@VM_77_25_centos java_test]$ java HelloWorld
Hello, World!
[wkq@VM_77_25_centos java_test]$ javap -v HelloWorld.class
public class HelloWorld{
	public static void main(String[] args){
		System.out.println("Hello, World!");
	}
}

反编译后的内容如下:

Classfile /home/wkq/workspaces/java_test/HelloWorld.class
  Last modified Jun 21, 2020; size 427 bytes
  MD5 checksum 15d19d1074b862ce9cca6a8a7f6ee8fe
  Compiled from "HelloWorld.java"
public class HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // Hello, World!
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // HelloWorld
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               HelloWorld.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               Hello, World!
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               HelloWorld
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public HelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello, World!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 4: 0
        line 5: 8
}
SourceFile: "HelloWorld.java"
[wkq@VM_77_25_centos java_test]$  vi CountPlus.java
[wkq@VM_77_25_centos java_test]$
[wkq@VM_77_25_centos java_test]$ javac CountPlus.java
[wkq@VM_77_25_centos java_test]$ java CountPlus
2
[wkq@VM_77_25_centos java_test]$
[wkq@VM_77_25_centos java_test]$ javap -v CountPlus.class
public class CountPlus {
    public static void main(String[] args) {
        int x = 1;
        x++;
        System.out.println(x);
    }

}
Classfile /home/wkq/workspaces/java_test/CountPlus.class
  Last modified Jun 21, 2020; size 401 bytes
  MD5 checksum a5d707b751fcd06e1c14854e84aafe07
  Compiled from "CountPlus.java"
public class CountPlus
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // CountPlus
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               CountPlus.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               CountPlus
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public CountPlus();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_1
         1: istore_1
         2: iinc          1, 1
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: iload_1
         9: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        12: return
      LineNumberTable:
        line 3: 0
        line 4: 2
        line 5: 5
        line 6: 12
}
SourceFile: "CountPlus.java"

References

[1] 字节码增强技术探索
https://cyw3.github.io/YalesonChan/2016/Java-Byte-Code.html
https://juejin.im/post/5aca2c366fb9a028c97a5609

Redis的各项功能解决了哪些问题?

先看一下Redis是一个什么东西。官方简介解释到:Redis是一个基于BSD开源的项目,是一个把结构化的数据放在内存中的一个存储系统,你可以把它作为数据库,缓存和消息中间件来使用。同时支持strings,lists,hashes,sets,sorted sets,bitmaps,hyperloglogs和geospatial indexes等数据类型。它还内建了复制,lua脚本,LRU,事务等功能,通过redis sentinel实现高可用,通过redis cluster实现了自动分片。以及事务,发布/订阅,自动故障转移等等。

阅读全文 »

前几天同事在晚上上线的时候执行sql语句造成锁表,想总结一下以避免后续发生。

(1) 遇到锁表快速解决办法

  依次执行1-6步,运行第6步生成的语句即可。

  如果特别着急,运行 1 2 6 步 以及第6步生成的kill语句 即可。

第1步 查看表是否在使用。

show open tables where in_use > 0 ;
如果查询结果为空。则证明表没有在使用。结束。

mysql>  show open tables where in_use > 0 ;
Empty set (0.00 sec)

如果查询结果不为空,继续后续的步骤。

mysql>  show open tables where in_use > 0 ;
+----------+-------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| test     | t     |      1 |           0 |
+----------+-------+--------+-------------+
1 row in set (0.00 sec)

 

第2步 查看数据库当前的进程,看一下有无正在执行的慢SQL记录线程。

show processlist;
注意:show processlist 是显示用户正在运行的线程,需要注意的是,除了 root 用户能看到所有正在运行的线程外,其他用户都只能看到自己正在运行的线程,看不到其它用户正在运行的线程。
SHOW PROCESSLIST shows which threads are running. If you have the PROCESS privilege, you can see all threads. Otherwise, you can see only your own threads (that is, threads associated with the MySQL account that you are using). If you do not use the FULL keyword, only the first 100 characters of each statement are shown in the Info field.  

第3步 当前运行的所有事务

SELECT * FROM information_schema.INNODB_TRX;  

第4步 当前出现的锁

SELECT * FROM information_schema.INNODB_LOCKs;  

第5步 锁等待的对应关系

SELECT * FROM information_schema.INNODB_LOCK_waits;
看事务表INNODB_TRX,里面是否有正在锁定的事务线程,看看ID是否在show processlist里面的sleep线程中,如果是,就证明这个sleep的线程事务一直没有commit或者rollback而是卡住了,我们需要手动kill掉。
搜索的结果是在事务表发现了很多任务,这时候最好都kill掉。  

第6步 批量删除事务表中的事务

这里用的方法是:通过information_schema.processlist表中的连接信息生成需要处理掉的MySQL连接的语句临时文件,然后执行临时文件中生成的指令。

SELECT concat('KILL ',id,';') 
FROM information_schema.processlist p 
INNER JOIN  information_schema.INNODB_TRX x 
ON p.id=x.trx_mysql_thread_id 
WHERE db='test';

记得修改对应的数据库名。

这个语句执行后结果如下:

mysql>  SELECT concat('KILL ',id,';')  FROM information_schema.processlist p  INNER JOIN  information_schema.INNODB_TRX x  ON p.id=x.trx_mysql_thread_id  WHERE db='test';
+------------------------+
| concat('KILL ',id,';') |
+------------------------+
| KILL 42;               |
| KILL 40;               |
+------------------------+
2 rows in set (0.00 sec)

执行结果里的两个kill语句即可解决锁表。  

阅读全文 »

数据库锁设计的初衷是处理并发问题。作为多用户共享的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则。而锁就是用来实现这些访问规则的重要数据结构。

(1) MySQL中的锁

按照加锁机制(是否锁住同步资源)分为乐观锁悲观锁

按照锁粒度可以分为全局锁表级锁行锁

按照多个线程是否共享分为共享锁排他锁

(1.1) 两段锁协议

有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。这种方式可以有效的避免循环死锁,但在数据库中却不适用,因为在事务开始阶段,数据库并不知道会用到哪些数据。

数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)

加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。


(2) 乐观锁悲观锁

MySQL中提供的锁都是悲观锁,需要先锁住才操作。


(3) MySQL中的全局锁表级锁和行锁

根据加锁的粒度/范围,MySQL 里面的锁大致可以分成全局锁表级锁行锁三类。

(3.1) 全局锁

全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。

全局锁的典型使用场景是,做全库逻辑备份。

风险:
1.如果在主库备份,在备份期间不能更新,业务停摆
2.如果在从库备份,备份期间不能执行主库同步的binlog,导致主从延迟

官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。

全局锁主要用在逻辑备份过程中。对于全部是 InnoDB 引擎的库,建议你选择使用 –single-transaction 参数,对应用会更友好。


(3.2) 表级锁

MySQL里表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。

(3.2.1) 表锁

表锁是在Server层实现的。ALTER TABLE之类的语句会使用表锁,忽略存储引擎的锁机制。

** 表锁的语法是 lock tables … read/write。** 与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
举个例子, 如果在某个线程 A 中执行 lock tables t1 read, t2 write; 这个语句,则其他线程写 t1、读写 t2 的语句都会被阻塞。同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。连写 t1 都不允许,自然也不能访问其他表。
在还没有出现更细粒度的锁的时候,表锁是最常用的处理并发的方式。而对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面还是太大。


(3.2.1) 元数据锁(metadata lock)

** 另一类表级的锁是 MDL(metadata lock)。 **

MDL 不需要显式使用,在访问一个表的时候会被自动加上。
MDL 的作用是并发情况下维护数据的一致性,保证读写的正确性。(避免加字段删字段导致查询结果异常)

因此,在 MySQL 5.5 版本中引入了 MDL,当对一个表做增删改查操作的时候,加 MDL 读锁;
当要对表做结构变更操作的时候,加 MDL 写锁。

读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。
因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

事务中的 MDL 锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放。

给一个表加字段,或者修改字段,或者加索引,需要扫描全表的数据。
而实际上,即使是小表,操作不慎也会出问题。在修改表的时候会持有MDL写锁,如果这个表上的查询语句频繁,而且客户端有重试机制,也就是说超时后会再起一个新 session 再请求的话,这个库的线程很快就会爆满。

MDL是并发情况下维护数据的一致性,在表上有事务的时候,不可以对元数据经行写入操作,并且这个是在server层面实现的


(3.3) 行锁

MySQL 的行锁是在引擎层由各个引擎自己实现的。
但并不是所有的引擎都支持行锁,比如 MyISAM 引擎就不支持行锁。
InnoDB 是支持行锁的,这也是 MyISAM 被 InnoDB 替代的重要原因之一。
InnoDB行锁包括 Record LockGap LockNext-Key Lock

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

知道了这个设定,对我们使用事务有什么帮助呢?那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

假设你负责实现一个电影票在线交易业务,顾客 A 要在影院 B 购买电影票。我们简化一点,这个业务需要涉及到以下操作:

  1. 从顾客 A 账户余额中扣除电影票价;
  2. 给影院 B 的账户余额增加这张电影票价;
  3. 记录一条交易日志。

试想如果同时有另外一个顾客 C 要在影院 B 买票,那么这两个事务冲突的部分就是语句 2 了。因为它们要更新同一个影院账户的余额,需要修改同一行数据。
根据两阶段锁协议,不论你怎样安排语句顺序,所有的操作需要的行锁都是在事务提交的时候才释放的。所以,如果你把语句 2 安排在最后,比如按照 3、1、2 这样的顺序,那么影院账户余额这一行的锁时间就最少。这就最大程度地减少了事务之间的锁等待,提升了并发度。


(4) MySQL数据库InnoDB存储引擎加锁过程

Read Committed (RC)
Repeatable Read (RR)

InnoDB的加锁分析前提条件
前提一: 查询列是不是主键?
前提二: 当前系统的隔离级别是什么?
前提三: 查询列上有索引吗?
前提四: 查询列是唯一索引吗?
前提五: 两个SQL的执行计划是什么?索引扫描?全表扫描?

update t1 set update_time = now() where k = 10 ;

组合一: k列是主键,RC隔离级别
组合二: k列是二级唯一索引,RC隔离级别
组合三: k列是二级非唯一索引,RC隔离级别
组合四: k列上没有索引,RC隔离级别
组合五: k列是主键,RR隔离级别
组合六: k列是二级唯一索引,RR隔离级别
组合七: k列是二级非唯一索引,RR隔离级别
组合八: k列上没有索引,RR隔离级别
组合九: Serializable隔离级别

组合一: Read Committed 隔离级别,k列是主键,给定SQL:update t1 set update_time = now() where k = 10; 只需要将主键上 k = 10的记录加上X锁即可

组合二: Read Committed 隔离级别,k列有unique索引,unique索引上的k=10一条记录加上X锁,同时,会根据读取到的列,回主键索引(聚簇索引),然后将聚簇索引上对应的主键索引项加X锁。

组合三: Read Committed 隔离级别,k列上有索引,那么对应的所有满足SQL查询条件的记录,都会被加锁。同时,这些记录在主键索引上的记录,然后将聚簇索引上对应的主键索引项加X锁。

组合四: Read Committed 隔离级别,若k列上没有索引,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因此每条记录,无论是否满足条件,都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放锁动作不会省略。同时,优化也违背了2PL的约束。

组合五: Repeatable Read 隔离级别,k列是主键列,给定SQL update t1 set update_time = now() where k = 10; 只需要将主键上 k = 10的记录加上X锁即可。

组合六: Repeatable Read 隔离级别,k列有unique索引,unique索引上的k=10一条记录加上X锁,同时,会根据读取到的列,回主键索引(聚簇索引),然后将聚簇索引上对应的主键索引项加X锁。

组合七:Repeatable Read 隔离级别,k列有索引, 通过索引定位到第一条满足查询条件的记录,加记录上的X锁,加GAP上的GAP锁,然后加主键聚簇索引上的记录X锁,然后返回;然后读取下一条,重复进行。直至进行到第一条不满足条件的记录,此时,不需要加记录X锁,但是仍旧需要加GAP锁,最后返回结束。

考虑到B+树索引的有序性,满足条件的项一定是连续存放的。如果要插入一条记录,肯定会插入在相同位置,为了保证两次查询查到的值一致,MySQL选择了用GAP锁,将 查询值范围前、查询值范围、查询值范围后 三个GAP给锁起来。

GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。

 k           | 7  | 8  | 10 | 10 | 40 | 50 |
primary id   | 1  | 2  | 3  | 4  | 5  | 6  |

为了保证[8,2]与[10,3]间,[10,3]与[10,4]间,[10,4]与[40,5]不会插入新的满足条件的记录,MySQL选择了用GAP锁,将这三个GAP给锁起来。

组合八: 在Repeatable Read隔离级别下,如果进行全表扫描的当前读,那么会锁上表中的所有记录,同时会锁上聚簇索引内的所有GAP,杜绝所有的并发 更新/删除/插入 操作
  聚簇索引上的所有记录,都被加上了X锁。其次,聚簇索引每条记录间的间隙(GAP),也同时被加上了GAP锁。

组合九:Serializable隔离级别下直接用加锁的方式来避免并行访问。

在RC,RR隔离级别下,都是快照读,不加锁。
Serializable隔离级别,读不加锁就不再成立,所有的读操作,都是当前读。

(4) 死锁

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。

当出现死锁以后,有两种策略:

  1. 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。
  2. 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。

在 InnoDB 中,innodb_lock_wait_timeout 的默认值是 50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。
可以考虑通过将一行改成逻辑上的多行来减少锁冲突。还是以影院账户为例,可以考虑放在多条记录上,比如 10 个记录,影院的账户总额等于这 10 个记录的值的总和。这样每次要给影院账户加金额的时候,随机选其中一条记录来加。这样每次冲突概率变成原来的 1/10,可以减少锁等待个数,也就减少了死锁检测的 CPU 消耗。

如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁的申请时机尽量往后放。

(5) 可能遇到的问题

(5.1) 备份一般都会在备库上执行,你在用–single-transaction 方法做逻辑备份的过程中,如果主库上的一个小表做了一个 DDL,比如给一个表上加了一列。这时候,从备库上会看到什么现象呢?

备份一般都会在备库上执行,你在用–single-transaction 方法做逻辑备份的过程中,如果主库上的一个小表做了一个 DDL,比如给一个表上加了一列。这时候,从备库上会看到什么现象呢?

假设这个 DDL 是针对表 t1 的, 这里我把备份过程中几个关键的语句列出来:

Q1:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Q2:START TRANSACTION  WITH CONSISTENT SNAPSHOT/* other tables */
Q3:SAVEPOINT sp;
/* 时刻 1 */
Q4:show create table `t1`;
/* 时刻 2 */
Q5:SELECT * FROM `t1`;
/* 时刻 3 */
Q6:ROLLBACK TO SAVEPOINT sp;
/* 时刻 4 */
/* other tables */

在备份开始的时候,为了确保 RR(可重复读)隔离级别,再设置一次 RR 隔离级别 (Q1);
启动事务,这里用 WITH CONSISTENT SNAPSHOT 确保这个语句执行完就可以得到一个一致性视图(Q2);
设置一个保存点,这个很重要(Q3);
show create 是为了拿到表结构 (Q4),然后正式导数据 (Q5),回滚到 SAVEPOINT sp,在这里的作用是释放 t1 的 MDL 锁 (Q6)。当然这部分属于“超纲”,上文正文里面都没提到。
DDL 从主库传过来的时间按照效果不同,我打了四个时刻。题目设定为小表,我们假定到达后,如果开始执行,则很快能够执行完成。

参考答案如下:

  1. 如果在 Q4 语句执行之前到达,现象:没有影响,备份拿到的是 DDL 后的表结构。

  2. 如果在“时刻 2”到达,则表结构被改过,Q5 执行的时候,报 Table definition has changed, please retry transaction,现象:mysqldump 终止;

  3. 如果在“时刻 2”和“时刻 3”之间到达,mysqldump 占着 t1 的 MDL 读锁,binlog 被阻塞,现象:主从延迟,直到 Q6 执行完成。

  4. 从“时刻 4”开始,mysqldump 释放了 MDL 读锁,现象:没有影响,备份拿到的是 DDL 前的表结构。

(5.2) 删数据问题

如果你要删除一个表里面的前 10000 行数据,有以下三种方法可以做到:
第一种,直接执行 delete from T limit 10000;
第二种,在一个连接中循环执行 20 次 delete from T limit 500;
第三种,在 20 个连接中同时执行 delete from T limit 500。

你会选择哪一种方法呢?为什么呢?

方案一,事务相对较长,则占用锁的时间较长,会导致其他客户端等待资源时间较长。
方案二,串行化执行,将相对长的事务分成多次相对短的事务,则每次事务占用锁的时间相对较短,其他客户端在等待相应资源的时间也较短。这样的操作,同时也意味着将资源分片使用(每次执行使用不同片段的资源),可以提高并发性。
方案三,人为自己制造锁竞争,加剧并发量。

(5.3) 问题3

1.如何在死锁发生时,就把发生的sql语句抓出来?
2.在使用连接池的情况下,连接会复用.比如一个业务使用连接set sql_select_limit=1,释放掉以后.其他业务复用该连接时,这个参数也生效.请问怎么避免这种情况,或者怎么禁止业务set session?
3.很好奇双11的成交额,是通过redis累加的嘛?
4.不会改源码能成为专家嘛?

  1. show engine innodb status 里面有信息,不过不是很全…
  2. 5.7的reset_connection接口可以考虑一下
  3. 用redis的话,为了避免超卖需要增加了很多机制来保证。修改都在数据库里执行就方便点。前提是要解决热点问题
  4. 我认识几位处理问题和分析问题经验非常丰富的专家,不用懂源码,但是原理还是要很清楚的

(5.4) 转义导致死锁问题

前天在开发中,还遇到过一次死锁,是在一个批处理中,要删除1000条数据,5个线程,200条数据commit一次,
sol:delete from 表A where id =15426169754750004759008 STORAGEDB
(id是主键)
我同事解决了,说原因是id 是char 类型,但是没有加单引号,所以没有进入id索引中,然后锁表了,所以导致死锁。

这个问题的出现,应该是人为只要并发导致锁冲突吧?但是为什么不加单引号会死锁,加了单引号就能正常跑呢?


(6) 思考题

从并发角度看插入操作和更新操作哪个先执行

假设你负责实现一个电影票在线交易业务,顾客 A 要在影院 B 购买电影票。我们简化一点,这个业务需要涉及到以下操作:
1、从顾客 A 账户余额中扣除电影票价;
2、给影院 B 的账户余额增加这张电影票价;
3、记录一条交易日志。

也就是说,要完成这个交易,我们需要 update 两条记录,并 insert 一条记录。当然,为了保证交易的原子性,我们要把这三个操作放在一个事务中。
那么,你会怎样安排这三个语句在事务中的顺序呢?

根据两阶段锁协议,不论你怎样安排语句顺序,所有的操作需要的行锁都是在事务提交的时候才释放的。
操作2行锁冲突比较大,如果你把语句2安排在最后,比如按照 3、1、2 这样的顺序,那么操作2影院账户余额这一行的锁时间就最少。
这就最大程度地减少了事务之间的锁等待,提升了并发度。

这么设计,影院余额这一行的行锁在一个事务中不会停留很长时间。

从并发系统性能的角度考虑,你觉得在这个事务序列里,应该先插入操作记录,还是应该先更新计数表呢?

知识点在《行锁功过:怎么减少行锁对性能的影响?》
因为更新计数表涉及到行锁的竞争,先插入再更新能最大程度地减少了事务之间的锁等待,提升了并发度。

参考资料

[1] 19 | 为什么我只查一行的语句,也执行这么慢?MySQL实战45讲
[2] 06 | 全局锁和表锁 :给表加个字段怎么有这么多阻碍?MySQL实战45讲
[3] 07 | 行锁功过:怎么减少行锁对性能的影响?MySQL实战45讲
[4] mysql 5.7 lock-tables
[5] MySQL 5.7 Reference Manual / The InnoDB Storage Engine / Locks Set by Different SQL Statements in InnoDB
[6] MySQL 5.7 Reference Manual / The InnoDB Storage Engine / InnoDB Startup Options and System Variables
[7] 《高性能MySQL》 O’REILLY
[8] mysql-show-open-tables
[9] mysql-show-processlist
[10] innodb-locking
[11] innodb-index-types
[12] 面试官:同学,分析一下MySQL/InnoDB的加锁过程吧
[13] 解决死锁之路 - 常见 SQL 语句的加锁分析
[14] mysql insert锁机制
[15] MySQL六十六问,两万字+五十图详解!
[16] Innodb中的事务隔离级别和锁的关系

转自 Java核心技术36讲 杨晓峰

第1讲 | 谈谈你对Java平台的理解?

模块一 Java基础 (14讲)

第1讲 | 谈谈你对Java平台的理解?

谈谈你对 Java 平台的理解?“Java 是解释执行”,这句话正确吗?

Java 本身是一种面向对象的语言,最显著的特性有两个方面,一是所谓的“书写一次,到处运行”(Write once, run anywhere),能够非常容易地获得跨平台能力;另外就是 垃圾收集 (GC, Garbage Collection),Java 通过垃圾收集器(Garbage Collector)回收分配内存,大部分情况下,程序员不需要自己操心内存的分配和回收。

我们日常会接触到 JRE(Java Runtime Environment)或者 JDK(Java Development Kit)。 JRE,也就是 Java 运行环境,包含了 JVM 和 Java 类库,以及一些模块等。而 JDK 可以看作是 JRE 的一个超集,提供了更多工具,比如编译器、各种诊断工具等。

对于“Java 是解释执行”这句话,这个说法不太准确。我们开发的 Java 的源代码,首先通过 Javac 编译成为字节码(bytecode),然后,在运行时,通过 Java 虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。但是常见的 JVM,比如我们大多数情况使用的 Oracle JDK 提供的 Hotspot JVM,都提供了 JIT(Just-In-Time)编译器,也就是通常所说的动态编译器,JIT 能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于

对于 Java 平台的理解,可以从很多方面简明扼要地谈一下,例如:Java 语言特性,包括泛型、Lambda 等语言特性;基础类库,包括集合、IO/NIO、网络、并发、安全等基础类库。对于我们日常工作应用较多的类库,面试前可以系统化总结一下,有助于临场发挥。

或者谈谈 JVM 的一些基础概念和机制,比如 Java 的类加载机制,常用版本 JDK(如 JDK 8)内嵌的 Class-Loader,例如 Bootstrap、 Application 和 Extension Class-loader;类加载大致过程:加载、验证、链接、初始化(这里参考了周志明的《深入理解 Java 虚拟机》,非常棒的 JVM 上手书籍);自定义 Class-Loader 等。还有垃圾收集的基本原理,最常见的垃圾收集器,如 SerialGC、Parallel GC、 CMS、 G1 等,对于适用于什么样的工作负载最好也心里有数。这些都是可以扩展开的领域,我会在后面的专栏对此进行更系统的介绍。

当然还有 JDK 包含哪些工具或者 Java 领域内其他工具等,如编译器、运行时环境、安全工具、诊断和监控工具等。这些基本工具是日常工作效率的保证,对于我们工作在其他语言平台上,同样有所帮助,很多都是触类旁通的。

Java特性:
面向对象(封装,继承,多态)
平台无关性(JVM运行.class文件)
语言(泛型,Lambda)
类库(集合,并发,网络,IO/NIO)
JRE(Java运行环境,JVM,类库)
JDK(Java开发工具,包括JRE,javac,诊断工具)

阅读全文 »

在写neo4j和orientdb的通用方法时,忽然想到jdbc,然后就想试试mysql neo4j orientdb几个数据库jdbc连接方式里的 prepartdStatement一不一样。

问题的来源来自以下代码

List<Map<String, Object>> list = new ArrayList();
try (PreparedStatement pst = conn.prepareStatement(sql);
     ResultSet rs = pst.executeQuery();) {

    List<String> fields = new ArrayList<>();
    while (rs.next()) {

        if (fields.isEmpty()) {
            ResultSetMetaData metaData = rs.getMetaData();
            // 查询出的字段
            int count = metaData.getColumnCount();
            for (int i = 1; i <= count; i++) {
                fields.add(metaData.getColumnName(i));
            }
        }

        Map<String, Object> map = new HashMap<>();
        for (String field : fields) {
            map.put(field, rs.getObject(field));
        }

        // T r =  JSONObject.parseObject(JSON.toString(map), Object.class);
        list.add(map);

    }
} catch (SQLException e) {
    throw new SQLException(e);
}
阅读全文 »
0%