MySQL事务隔离级别

对于数据库的隔离级别之前一直没有做详细整理,最近项目运行中发现了一个问题,所以抽时间对这块认真研究了下

业务场景:
服务A在处理流程中,会调用外部服务B,然后写入一条数据,服务B执行完成后,会回调服务C的接口更新服务A写入的数据。
问题:
在服务B回调服务C的时候总是找不到服务A写入的数据,在服务C中添加延时重试,问题依然存在,但此时查看数据库,对应的数据是已经存在。

先说原因吧,是因为MySQL的事务默认隔离级别是:可重复读。
在服务A调用服务B后,还没有写入数据到数据库,服务B就已经回调服务C了,服务C此时肯定是找不到对应的数据的,由于MySQL默认隔离级别是可重复读(即在一个事务中,对于同一份数据读取都是一样的),所以即使服务A已经写入了数据,服务C依然读取不到。

解决方案:
在服务C中的查询,不要放到一个事务里面,单独提取一个方法,后面的更新逻辑放到同一个事务中

什么是事务?

数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
比如:某人在商店购买100商品,其中包括两个操作:
1.该人账户减少100元
2.商店账户增加100元
这两个操作要么同时执行成功,要么同时执行失败。

数据库事务的ACID性质

  • Atomic:原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
  • Consistent:一致性,事务完成后,所有数据的状态都是一致的,即该人的账户减少了100,商店的账户必须增加100
  • Isolation:隔离性,比如两个人从同一个账户取款,这两个事务对数据的修改必须相互隔离,具体隔离策略后面具体讲解。
  • Duration:持久性,事务完成后,对数据库数据的修改必须被持久化存储。

数据库的隔离级别

在单个事务中,不需要做隔离,所谓数据库隔离级别是针对在并发事务的情况下,解决导致的一系列问题,这些问题包括:脏读、不可重复读、幻读,具体隔离级别如下图:

隔离级别 脏读 不可重复读 幻读
SERIALIZABLE(串行化) 避免 避免 避免
REPEATABLE READ(可重复读) 避免 避免 允许
READ COMMITED(读已提交) 避免 允许 允许
READ UNCOMMITED(读未提交) 允许 允许 允许

SERIALIZABLE(串行化)

当两个事务同时操作数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化方式运行。

REPEATABLE READ(可重复读)

一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。

READ COMMITTED(读已提交数据)

一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且还能看到其他事务已经提交的对已有记录的更新。

READ UNCOMMITTED(读未提交数据)

一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且还能看到其他事务没有提交的对已有记录的更新。

没有事务隔离级别导致的问题

如果没有数据库的隔离级别,数据库的数据是实时变化的,即每个事务都可以读到其它事务修改后的数据,下面结合实例介绍每个场景的问题。

脏读

定义:读到未提交更新的数据

时间点 事务1 事务2
T1 开始事务
T2 开始事务
T3 查询账户余额为1000
T4 取出100后金额为900
T5 查询账户金额为900(脏读)
T6 撤销事务,余额恢复为1000
T7 存入100元后,金额变为1000
T8 提交事务

如上事务1取出金额100后又回滚了,即啥都没做,但事务2存入了100,但最终的金额确还是1000,正确应该是1100。
在T5时间节点出现了脏读,如果数据库配置了隔离级别为SERIALIZABLE、REPEATABLE READ、READ COMMITTED,在事务1没有提交的时候,事务2读取的都是原来的值就不会出现问题。

不可重复读

定义:在同一个数据中,两次读取到的数据不一致,读到了其他数据提交更新的数据

时间点 事务1 事务2
T1 开始事务
T2 开始事务
T3 查询账户余额为1000
T4 查询账户余额为1000
T5 取出100后金额为900
T6 提交事务
T7 查询账户余额为900(与T4读取的不一致)

事务2的两次读取到的数据不一致,第二次读取到了事务1提交的数据

幻读

定义:读取到另一个事务已提交插入或删除的数据。

时间点 事务1 事务2
T1 开始事务
T2 开始事务
T3 统计一年级1班所有的学生人数为40人
T4 一年级1班新增一名学生
T5 提交事务
T6 再次统计一年级1班的所有学生人数为41人

事务2第一次统计人数为40人,第二次统计为41人,两次统计的结果不一致,同样如果T4时间节点转走一名学生,也会出现不一致

不可重复读和幻读看起来比较类似,都是一个事务里面读取到两次不同的结果
本质的区别是:不可重复读是由于数据更新导致数据不一致导致,幻读是由于插入或删除了数据导致的。

使用Sharing-JDBC实现分表

使用Sharing-JDBC实现分表

Sharing-JDBC介绍

在创建数据库时,我们最先考虑的是按模块对数据库进行划为,但即使这样,单表数据量还是出现大数量的情况,Sharing-JDBC可以对表进行水平切分,将数据均分到不同表中

通过日期水平切分

目前我们航班动态数据全球每天航班20多万,考虑到我们业务场景,用户都是通过航班号+日期来查询一个航班,所以我们采取使用日期来水平分表

添加依赖

<dependency>
	<groupId>com.dangdang</groupId>
	<artifactId>sharding-jdbc-core</artifactId>
	<version>1.5.4.1</version>
</dependency>
<dependency>
	<groupId>com.dangdang</groupId>
	<artifactId>sharding-jdbc-config-spring</artifactId>
	<version>1.5.4.1</version>
</dependency>
<!--mysql flight数据源 -->
<bean id="mysqlDataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
...
</bean>

<!--shared jdbc -->

<rdb:strategy id="tableShardingStrategy" sharding-columns="local_date" algorithm-class="com.huoli.songshan.sharding.FlyDateTableShardingAlgorithm" />
<rdb:data-source id="shardingDataSource">
	<rdb:sharding-rule data-sources="mysqlDataSource" default-data-source="mysqlDataSource">
		<rdb:table-rules>
			<rdb:table-rule logic-table="flight_info" actual-tables="flight_info_${2012..2020}${['01','02','03','04','05','06','07','08','09','10','11','12']}${0..3}${0..9}"
				table-strategy="tableShardingStrategy" />
		</rdb:table-rules>
		<rdb:default-database-strategy sharding-columns="none" algorithm-class="com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithm" />
	</rdb:sharding-rule>
	<rdb:props>
		<prop key="sql.show">false</prop>
	</rdb:props>
</rdb:data-source>

先配置一个dataSource数据源,这里我们使用的是hikari,再通过shardingDataSource对dataSource进行包装,按照表中localdate字段对表进行拆分,即表名依次为flightinfo20120101、flightinfo20120102,到flightinfo20201231,下面我们实现根据日期localdate映射到对应表上

public final class FlyDateTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Date> {
    private DateTimeFormatter dt = DateTimeFormat.forPattern("yyyyMMdd");

    @Override
    public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Date> shardingValue) {
        DateTime datetime = new DateTime(shardingValue.getValue());
        String flydate = datetime.toString(dt);
        for (String each : availableTargetNames) {
            if (each.endsWith(flydate)) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

    @Override
    public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<Date> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
        for (Date value : shardingValue.getValues()) {
            DateTime datetime = new DateTime(value);
            String flydate = datetime.toString(dt);
            for (String tableName : availableTargetNames) {
                if (tableName.endsWith(flydate)) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }

    @Override
    public Collection<String> doBetweenSharding(Collection<String> availableTargetNames,
            ShardingValue<Date> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
        Range<Date> range = (Range<Date>) shardingValue.getValueRange();
        for (Date value = range.lowerEndpoint(); value.before(range.upperEndpoint()) || value.equals(range.upperEndpoint()); value = addDays(value, 1)) {
            DateTime datetime = new DateTime(value);
            String flydate = datetime.toString(dt);
            for (String each : availableTargetNames) {
                if (each.endsWith(flydate)) {
                    result.add(each);
                }
            }
        }
        return result;
    }

    private Date addDays(Date date, int days) {
        return new Date(new DateTime(new DateTime(date).plusDays(days)).toDate().getTime());
    }
}

这里实现了SQL的equal、in、between方法,即通过使用select * from flight_info where local_date=’2019-01-01′,会自动映射flight_info_20190101表中查询。

通过用户ID哈希取模进行拆分

在用户订阅航班动态数据后,我们需要保存用户的订阅数据,以便后期推送动态消息给用户,用户可以在登陆的情况下订阅航班,也可以在未登陆的情况下订阅航班,用户登陆后可以拿到用户的phoneId(设备ID)和userId(用户ID),用户未登陆只能获取到用户的phoneId(设备ID),所以我们区分两种情况,使用两种表来存放用户信息trip_subscribe_nologin和trip_subscribe_login,用户未登陆根据phoneId来分表,用户登陆的情况根据userId来分表

和之前一样,我们需要先配置一个分表策略

<!-- trip_subscribe_nonlogin分表策略 -->
<rdb:strategy id="subscribeNonLoginTableShardingStrategy" sharding-columns="uid" algorithm-class="com.huoli.trip.dao.sharding.SubscribeNonLoginTableShardingAlgorithm" />
<!-- trip_subscribe_login分表策略 -->
<rdb:strategy id="subscribeLoginTableShardingStrategy" sharding-columns="user_id" algorithm-class="com.huoli.trip.dao.sharding.SubscribeLoginTableShardingAlgorithm" />

<!-- 分表配置配置 -->
<rdb:data-source id="shardingDataSource">
	<rdb:sharding-rule data-sources="mysqlDataSource">
		<rdb:table-rules>
			<rdb:table-rule logic-table="trip_subscribe_nonlogin" table-strategy="subscribeNonLoginTableShardingStrategy" actual-tables="trip_subscribe_nonlogin_${0..63}">
				<rdb:generate-key-column column-name="id" column-key-generator-class="com.huoli.trip.dao.sharding.TripKeyGenerator" />
			</rdb:table-rule>
			<rdb:table-rule logic-table="trip_subscribe_login" table-strategy="subscribeLoginTableShardingStrategy" actual-tables="trip_subscribe_login_${0..63}">
				<rdb:generate-key-column column-name="id" column-key-generator-class="com.huoli.trip.dao.sharding.TripKeyGenerator" />
			</rdb:table-rule>
		</rdb:table-rules>
		<rdb:default-database-strategy sharding-columns="none" algorithm-class="com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithm" />
	</rdb:sharding-rule>
	<rdb:props>
		<prop key="sql.show">true</prop>
	</rdb:props>
</rdb:data-source>

这里是根据phoneId、userId分别分成了64张表,即trip_subscribe_nologin有64张表(trip_subscribe_nologin_1,trip_subscribe_nologin_2 …),trip_subscribe_login有64张表(trip_subscribe_login_1,trip_subscribe_login_2 …),具体代码实现

public interface Shard<T> {

    /** 根据参数计算分片标识 */
    public T calculateShard(Object... args);
}

public class SubscribeNonLoginTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<String>, Shard<String> {

    /** 分片数量 */
    private final int shardNum = 64;

    public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<String> shardingValue) {
        String shard = calculateShard(shardingValue.getValue());
        for (String tableName : availableTargetNames) {
            if (tableName.endsWith("_" + shard)) {
                return tableName;
            }
        }

        throw new IllegalArgumentException("未找到该表:trip_subscribe_nonlogin_" + shard);
    }

    public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<String> shardingValue) {
        Collection<String> result = new LinkedHashSet<String>(availableTargetNames.size());
        for (String value : shardingValue.getValues()) {
            String shard = calculateShard(value);
            for (String tableName : availableTargetNames) {
                if (tableName.endsWith("_" + shard)) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }

    public Collection<String> doBetweenSharding(Collection<String> availableTargetNames, ShardingValue<String> shardingValue) {
        // 不会出现between两个uid之间的查询需求,所以无需实现该方法
        return new LinkedHashSet<String>(availableTargetNames.size());
    }

    public String calculateShard(Object... args) {
        String phoneId = (String) args[0];
        return "" + (phoneId.hashCode() & 0x7fffffff) % shardNum;
    }
}

public class SubscribeLoginTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<String>, Shard<String> {

    /** 分片数量 */
    private final int shardNum = 64;

    /**
     * doEqualSharding/doInSharding同SubscribeNonLoginTableShardingAlgorithm
     */

    public String calculateShard(Object... args) {
        String userId = (String) args[0];
        return "" + (userId.hashCode() & 0x7fffffff) % shardNum;
    }
}

这里主要介绍下calculateShard方法,首先是先拿到了phoneId或userId,获取hashCode,然后和0x7fffffff进行&运算,0x7fffffff表示long的最大值,这一步是为了保证得到的index的第一位为0,也就是为了得到一个正数。因为有符号数第一位0代表正数,1代表负数,然后和分片数shardNum,使表数据分布在shardNum内

另外这里还用到了id的生成策略,即生成一个全局的自增ID,sharing-jdbc自带了DefaultKeyGenerator生成器可以实现

public class TripKeyGenerator implements KeyGenerator {
    private static Logger logger = LoggerFactory.getLogger(TripKeyGenerator.class);

    private DefaultKeyGenerator defaultKeyGenerator;

    public TripKeyGenerator() {
        defaultKeyGenerator = new DefaultKeyGenerator();
    }

    static {
        /** 从配置中获取workId */
        DefaultKeyGenerator.setWorkerId(workerId);
    }

    @Override
    public synchronized Number generateKey() {
        return defaultKeyGenerator.generateKey();
    }

}

通过twitter的snowlflake也可以生成唯一ID,具体可以参考:这里

 

Redis导致接口变慢故障排查

Redis导致接口变慢故障排查

近段时间我们一个接口总是隔断时间出现一次访问很慢的情况,如下图,这是我们通过kibana统计的接口响应时间

WechatIMG12-1

最开始我们想到是不是并发量太大导致后端数据库压力太大了,所以多开了一个实例,并且数据读写采用了读写分离,但情况依旧,最后也打印出了操作数据库、Redis的耗时情况,如下图

WechatIMG13

发现MySQL并不是瓶颈,Redis读取的时候耗时比MySQL更严重,我们知道Redis是单线程直接操作内存的,一定是有某些操作阻碍了主线程的执行,查看了Redis执行日志

WechatIMG14

发现在耗时那个点,Redis正在做持久化操作,而且使用的是RDB全量快照的方式,介绍下RDB的持久化功能:
Redis默认是使用RDB的持久化策略,可以配置周期性将数据保存到磁盘,比如可配置在1分钟内发生1000次写操作,就保存一次,这里的保存是全量保存,即Redis会fork一个子进程来循环所有的数据,然后将数据写入到RDB文件中,如果在某个时间段有频繁的写请求过来,那么Redis就不不断的fork子进程来处理数据库快照操作,但fork操作会发生堵塞,所以那段时间就会发生客户端的读写请求比较卡的情况,Redis的持久化策略流程如下图:

3084708676-5b70e0fd04072_articlex

解决,使用AOP持久化策略或者我们可以配置不使用Redis持久化策略,因为根据接口的业务情况,发现即使数据丢失,也不会造成太大影响,可以直接再去读数据库获取,具体配置只要在最后一行加上:save “”,即可禁用RDB

参考:

http://www.cnblogs.com/zhoujinyi/archive/2013/05/26/3098508.html

https://segmentfault.com/a/1190000015983518

 

 

Mysql死锁的问题

Mysql死锁的问题

1,先看错误日志

WechatIMG21 WechatIMG22

从以上日志可以看出是两条SQL执行出现了问题,后面一条SQL回滚了

SQL为:

update dynamic_check_packet_system set check_flag = '3' where create_time <= '2018-12-19 02:00:00' and check_flag='0' and conflict_field not in ('suspectCancel');
update flight.dynamic_check_packet_system set check_flag='1', update_time='2018-12-19 09:55:00.0',check_start_time='2018-12-19 10:00:00' where id=14211034;

使用explain查看第一条SQL

E9C461C524DED9A648DC4DCFF089919F

可以看到,这个语句使用了idxcheckflag这个索引,createtime在前,为什么没有使用createtime?

查看表中<=createtime的数据,发现有14166149条,远远大于checkflag=0的记录数,这时MySQL就会优先选择使用chekflag的索引,所以第一条语句会把checkflag=0的所有记录数都锁住。

第二条SQL同样更新check_flag=0,id=14211034的记录,但这条记录是被第一条SQL锁住的,所以就会更新失败了?

深入分析:
这里查看MySQL引擎,用的是Innodb,Innodb是支持行锁的,既然第一条SQL把这条记录行锁住了,第二条SQL应该等待才对,为什么会发生死锁呢?所以这里一定存在两把锁,而且锁的顺序不同。

我们知道MySQL的Innodb主键使用了聚集索引(索引直接指向实际数据),而如果再新建一个索引,这个索引会指向主键索引,然后通过主键索引找到数据,所以这里存在需要更新聚集索引ID数据和二级索引check_flag数据

第一条语句通过checkflag=0查找,那么就先会锁住二级索引checkflag数据,然后再去获取聚集索引ID数据的锁

而第二天SQL则是通过ID查找,那么就会先会锁住聚集索引ID数据,然后再去获取二级索引check_flag数据

显然这样获取锁的先后顺序不同,就造成了死锁。

问题解决:
让第一条SQL语句使用createtime索引,可以指定一个createtime>’开始时间’ and createtime<’结束时间’来减少扫描行数,让MySQL优先使用createtime索引。

 

Mysql插入表情字符问题处理

最近在处理微信数据时,出现如下的错误:

ERROR[12-02 11:11:57]cn.hl.basic.datapersist.DbBasicService.execSql(DbBasicService.java:632): java.sql.SQLException: Incorrect string value: '\xF0\x9F\x8D\xAC",...' for column 'user_info' at row 1

错误是在获取微信用户信息后,插入数据库时发生的,基本可以判断是微信用户名中有特殊字符,所以导致数据插入不了

在网上也找资源半天,说是编码问题,需要调整编码为utf8mb4,但看了数据库这个字段的编码,却就是utf8mb4的,又看了些文章,网上有说要改MySQL服务端的编码,也就是修改配置,然而服务器的配置不是那么好动的,现在运行的项目有很多。

是否是我连接的时候编码就指定错了,或是可以通过连接URL来指定呢?这样想着,查看了MySQL的连接URL,目前的如下:

jdbc:mysql://ipandport/dbname?useOldAliasMetadataBehavior=true&autoReconnect=true&failOverReadOnly=false&characterEncoding=utf8

不是说使用utf8mb4吗?这里用的utf8,会影响吗?是这个问题吗?带着问题,又查看了网上的资料,

明白了一个问题,这些乱码是微信中的表情符,这些表情符是占4个字节的,而utf8却三个字节的,如果在连接URL中指定characterEncoding=utf8的话,那么他写入数据时就按照3个字节写入了,那肯定写不进去的,有不指定写入字节,按照本身的字符的字节来写吗?

有!!!就是useUnicode参数,设置为useUnicode=true,那么在写入数据时,会根据数据本身的字节来写,好吧,明白了问题的原因,果断试下,果然不出所料,实践证明了预想的结论。

说明:useUnicode的作用:

当设置useUnicode=true时,数据在存入数据库时会根据数据库字段的编码进行转换后,再存储。所以数据库的编码设置为utf8mb4,那么存储肯定没啥问题了。

正确的配置如下:

jdbc:mysql://ipandport/dbname?useOldAliasMetadataBehavior=true&autoReconnect=true&failOverReadOnly=false&userUnicode=true

以下为再网上查询的一些资料,放在这里,以供参考:

http://ourmysql.com/archives/1402

http://www.jianshu.com/p/20740071d854

网上还说了另一种方案,就是在知道会有这样字符的字段时,统一对其进行转码,比如转成base64的存入到数据库,取出时再统一转码,但是对于之前数据库中已经存在历史大量的数据,操作是很麻烦的,还要去刷数据,所以在连接指定字符是最简单可行的方法。

另外说下,对于有这样的字符,插入到数据库中的时候会变成?,不用担心,取出来后数据还是可以还原的。

7e5771da-804a-49c1-948d-42c83ad8a9aa

9a84e520-7b2b-468b-85e3-24e14962146a

关于join查询使用遇到的问题

今天发现服务器上的一个sql执行非常慢,两张关联表查询

有一张关注表user_focus,和一张关注备注表user_focus_note

sql如下:

select t1.FLYID,t1.FLYKEY,t1.ISPUSH,t1.NOTIFYSTATE,t2.description,t2.remind_times from USER_FOCUS t1 left join USER_FOCUS_note t2 on t1.ID=t2.user_focus_id where (t1.USERID=’1378416183′ or t1.PHONEID=’18820063′) and t1.ORDERTYPE=’0′ and t1.NOTIFYSTATE=’0′ and t1.UPDATETIME >= ’2016-09-24 00:00:00′ UNION select t3.FLYID,t3.FLYKEY,t3.ISPUSH,t3.NOTIFYSTATE,t4.description,t4.remind_times from USER_FOCUS t3 left join USER_FOCUS_note t4 on t3.ID=t4.user_focus_id where (t3.USERID=’1378416183′ or t3.PHONEID=’18820063′) and t3.ORDERTYPE=’0′ and t3.NOTIFYSTATE in (1,2) and t3.UPDATETIME >= ’2016-10-24 09:55:19′;

进一步分析,发现下面sql执行很慢,基本上再20s以上,但返回结果只有1000多条

select t1.FLYID,t1.FLYKEY,t1.ISPUSH,t1.NOTIFYSTATE,t2.description,t2.remind_times from USER_FOCUS t1 left join USER_FOCUS_NOTE t2 on t1.ID=t2.user_focus_id where (t1.USERID=’1378416183′ or t1.PHONEID=’18820063′) and t1.ORDERTYPE=’0′ and t1.NOTIFYSTATE=’0′ and t1.UPDATETIME >= ’2016-09-24 00:00:00′

其中user_focus有1600万条数据,PHONEID,USERID,UPDATETIME 都建了索引,

去掉关联查询后,速度一下子就提升上来了,基本100ms就执行完了,所以确定是使用left join的问题,查看了下user_focus_note表,发现有18w条数据,数据量不大啊,怎么回事?

仔细查看了sql,这里有用到t1.ID=t2.user_focus_id,USER_FOCUS_NOTE 表user_focus_id建立了一个索引,重新执行sql,速度果然很快。USER_FOCUS 表虽然只有1000多条数据满足,但是查询USER_FOCUS_NOTE表时都需要扫描全表,这样就有180000*1000条扫描,所以查询肯定很慢

总结:类似两个大表,或一个大表、一个小表做关联查询时,一定要建立好索引。

 

mysql存储引擎InnoDB与MyISAM比较

 

InnoDBMyisam的六大区别

MyISAM InnoDB
构 成上的区别: 每个MyISAM在磁盘上存储成三个文件。第一个 文件的名字以表的名字开始,扩展名指出文件类型。.frm文件存储表定义。数据文件的扩 展名为.MYD (MYData)。索引文件的扩 展名是.MYI (MYIndex)。MyISAM备份数据只需要将以上三个文件导出即可,Innodb需要导出为sql语句 基于磁盘的资源是InnoDB表空间数据文件和它的日志文件,InnoDB 表的 大小只受限于操作系统文件的大小,一般为 2GB
事务处理上方面: MyISAM类型的表强调的是性能,其执行数 度比InnoDB类型更快,但是不提供事务支持 InnoDB提供事务支持事务,外部键等高级 数据库功能
SELECT UPDATE,INSERTDelete操 作 如果执行大量的SELECT,MyISAM是更好的选择 1.如果你的数据执行大量的INSERTUPDATE,出于性能方面的考虑,应该使用InnoDB表2.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的 删除。3.LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用
AUTO_INCREMENT的 操作 每表一个AUTO_INCREMEN列的内部处理。MyISAMINSERTUPDATE操 作自动更新这一列。这使得AUTO_INCREMENT列更快(至少10%)。在序列顶的值被删除之后就不 能再利用。(当AUTO_INCREMENT列被定义为多列索引的最后一列, 可以出现重使用从序列顶部删除的值的情况)。AUTO_INCREMENT值可用ALTER TABLE或myisamch来重置对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但 是在MyISAM表中,可以和其他字段一起建立联 合索引更好和更快的auto_increment处理 如果你为一个表指定AUTO_INCREMENT列,在数据词典里的InnoDB表句柄包含一个名为自动增长计数 器的计数器,它被用在为该列赋新值。自动增长计数 器仅被存储在主内存中,而不是存在磁盘上关于该计算器 的算法实现,请参考AUTO_INCREMENT列 在InnoDB里 如何工作
表 的具体行数 select count(*) from table,MyISAM只要简单的读出保存好的行数,注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的 InnoDB 中不 保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行
表锁 提供行锁(locking on row level),提供与 Oracle 类型一致的不加锁读取(non-locking read in
SELECTs),另外,InnoDB表的行锁也不是绝对的,如果在执 行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如update table set num=1 where name like “%a%”

MySQL用户管理

1. 用户添加及分配权限

1)登录mysql

#mysql  -uroot –p

 2)添加用户

 mysql> user mysql   //使用mysql数据库

mysql> grant all on *.* to zhangchuan@”%” identified by “123″ ;

 (执行完会在mysql.user表插入一条记录,all表示所有权限(包括增 删 改 查

等权限); *.* 表示所有数据库,zhangchuan为添加的用户名,123为密码,

%为匹配的所有主机,上面的信息都可以指定如grant select,update on db.* to

zhangchuan @localhost identified by ’123″;)

mysql> flush privileges;  //刷新系统权限表

3)修改用户

mysql> update user set user=’mysqladminqwe’ where user=’root’;

mysql> flush privileges;

4)删除用户

mysql> delete from mysql.user where user =’zhangchuan’ ;

mysql> flush privileges;

注:执行完sql语句后必须执行 flush privileges才可生效

2. 密码修改:

1)使用mysqladmin命令

# mysqladmin -u root password oldpass “newpass”

2)登录mysql客户端操作mysql.user

#mysql –u root

mysql> user mysql

mysql> UPDATE user SET Password = PASSWORD(‘newpass’) WHERE user = ‘root’;

mysql> flush privileges;

3. 忘记密码

1) 停止mysql服务

# /etc/init.d/mysqld stop

或: service mysqld stop

2)进入mysql的bin目录执行以下操作

# cd /usr/local/mysql/bin

# ./mysqld_safe –user=mysql –skip-grant-tables –skip-networking &

3)进入mysql客户端修改密码

# mysql -u root mysql

mysql> UPDATE user SET Password=PASSWORD(‘newpassword’)

where USER=’root’;

mysql> FLUSH PRIVILEGES;

mysql> quit

4)启动mysql服务登录

# /etc/init.d/mysql restart

# mysql -uroot -p

Enter password: <输入新设的密码newpassword>

4. 开放外网权限

上面已经说过,可以使用grant命令,不过我遇到使用grant开放权限后,使用本地客户端却连接不上。

解决方法如下:

1)检查3306端口是否开放给指定IP

使用nestat命令查看3306端口状态:

~# netstat -an | grep 3306

tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN

若出现以上结果表示3306端口只是在IP 127.0.0.1上监听,所以拒绝了其他IP的访问。

 2)修改/etc/mysql/my.cnf文件。打开文件,找到下面内容:

# Instead of skip-networking the default is now to listen only on

# localhost which is more compatible and is not less secure.

bind-address = 127.0.0.1

把上面这一行注释掉或者把127.0.0.1换成合适的IP,建议注释掉。

重新启动后,重新使用netstat检测:

~# netstat -an | grep 3306

tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN

清除mysql数据同步文件

1. 按文件:删除mysqld-bin.000123之前的日志,不包含mysqld-bin.000123

MYSQL>purge binary logs to ‘mysqld-bin.000123′;

Query OK, 0 rows affected (0.16 sec)

2. 按时间:删除2012-12-20 00:00:00 之前的日志

MYSQL>purge binary logs before ’2012-12-20 00:00:00′;

3. 按时间:请理三天之前的日志

MYSQL> purge master logs before date_sub(now(), interval 3 day);

自动清理日志 :

4. 修改my.cnf文件配置bin-log过期时间

[mysqld]

expire-logs-days=3

表示删除3天之前的数据