java - MySQL 在 Preparing Statement 中以非常奇怪的行为被阻止

标签 java mysql query-optimization

就像我解释的那样before ,我的 mysql 数据库服务器出现了一些问题。我会知道您的意见并有一些想法,因为我处于黑洞中,我不知道,因为正在发生服务器的行为。

我会尽量解释所有的环境。我有 1 个数据库,有很多表。我们用 java 制作了一个导出器工具,可以从数据库中导出所有数据。数据存储在5个不同的表中,我需要将数据连接到5个表中。这些是表格:

DB的结构是一个从一些传感器接收信息并存储的系统。

测量表:我们从传感器接收到的测量值。

+--------------------+------------+------+-----+---------+----------------+
| Field              | Type       | Null | Key | Default | Extra          |
+--------------------+------------+------+-----+---------+----------------+
| id                 | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version            | bigint(20) | NO   |     | NULL    |                |
| counter            | char(2)    | YES  |     | NULL    |                |
| datemeasurement_id | datetime   | NO   | MUL | NULL    |                |
| datereal_id        | datetime   | NO   | MUL | NULL    |                |
| delayed            | bit(1)     | NO   |     | NULL    |                |
| frequency          | tinyint(4) | YES  |     | NULL    |                |
| measuringentity_id | bigint(20) | NO   | MUL | NULL    |                |
| real               | bit(1)     | NO   |     | NULL    |                |
| tamper             | bit(1)     | NO   |     | NULL    |                |
| value              | float      | NO   |     | NULL    |                |
+--------------------+------------+------+-----+---------+----------------+

measuring_entity 表:一个传感器可以测量不止一件事(温度、湿度)。这些就是实体。

+--------------+------------+------+-----+---------+----------------+
| Field        | Type       | Null | Key | Default | Extra          |
+--------------+------------+------+-----+---------+----------------+
| id           | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version      | bigint(20) | NO   |     | NULL    |                |
| household_id | varchar(4) | NO   | MUL | NULL    |                |
| operative    | bit(1)     | NO   |     | NULL    |                |
| type         | char(20)   | NO   |     | NULL    |                |
| unit         | char(3)    | NO   |     | NULL    |                |
| interval     | float      | YES  |     | NULL    |                |
+--------------+------------+------+-----+---------+----------------+

sensor_measuring_entity:一个传感器可以关联多个实体。

+--------------------+------------+------+-----+---------+-------+
| Field              | Type       | Null | Key | Default | Extra |
+--------------------+------------+------+-----+---------+-------+
| sensor_id          | bigint(20) | NO   | PRI | NULL    |       |
| measuringentity_id | bigint(20) | NO   | PRI | NULL    |       |
| version            | bigint(20) | NO   |     | NULL    |       |
+--------------------+------------+------+-----+---------+-------+

传感器表:传感器的信息,与上表中的测量实体相关。

+---------------------+-------------+------+-----+---------+----------------+
| Field               | Type        | Null | Key | Default | Extra          |
+---------------------+-------------+------+-----+---------+----------------+
| id                  | bigint(20)  | NO   | PRI | NULL    | auto_increment |
| version             | bigint(20)  | NO   |     | NULL    |                |
| battery             | bit(1)      | NO   |     | NULL    |                |
| identifier          | char(6)     | NO   |     | NULL    |                |
| installationdate_id | datetime    | NO   | MUL | NULL    |                |
| lastreceiveddate_id | datetime    | YES  | MUL | NULL    |                |
| location_id         | bigint(20)  | NO   | MUL | NULL    |                |
| operative           | bit(1)      | NO   |     | NULL    |                |
| tampererror         | smallint(6) | NO   |     | NULL    |                |
+---------------------+-------------+------+-----+---------+----------------+

位置表:传感器的放置位置。

+------------+------------+------+-----+---------+----------------+
| Field      | Type       | Null | Key | Default | Extra          |
+------------+------------+------+-----+---------+----------------+
| id         | bigint(20) | NO   | PRI | NULL    | auto_increment |
| version    | bigint(20) | NO   |     | NULL    |                |
| height     | tinyint(4) | YES  |     | NULL    |                |
| operative  | bit(1)     | NO   |     | NULL    |                |
| place      | char(15)   | NO   | MUL | NULL    |                |
| room       | char(15)   | NO   |     | NULL    |                |
| typesensor | char(15)   | NO   |     | NULL    |                |
| formaster  | bit(1)     | YES  |     | NULL    |                |
+------------+------------+------+-----+---------+----------------+

导出信息的算法很重要,就像这样,交叉数据,尝试为我们可以在单独的 csv 文件中拥有的所有类型的传感器导出单独的信息。:

for (int i = 0; i < households.length; i++) {

openConnection();

for (int j = 0; j < values.length; j++) {

    for (int k = 0; k < rooms.length; k++) {

        if (places.length > 0) {

        for (int l = 0; l < places.length; l++) {

            for (int m = 0; m < height.length; m++) {

                export(startDate2, endDate,
                    households[i], values[j],
                    rooms[k], places[l],height[m]);
                }
            }
        } else {
            for (int m = 0; m < height.length; m++) {

                export(startDate2, endDate,
                    households[i], values[j],
                    rooms[k], null, height[m]);
            }
        }

    }
}

try {
    connection.close();
} catch (SQLException e1) {
    e1.printStackTrace();
}

        }


public void export(String startTime, String endTime, String household,
        String type, String room, String place, String height)
        throws ExporterException {


    String sql = buildSQLStatement(startTime, endTime, household, type,
            room, place, height);

    Statement query;

    try {
        query = connection.createStatement();
        ResultSet result = query.executeQuery(sql); 
        …
        (The exporting to csv code)
        …





private String buildSQLStatement(String startTime, String endTime,
        String household, String type, String room, String place,
        String height) {


    String sql = "select HIGH_PRIORITY m.datemeasurement_id, me.type, l.place, m.value, l.room, l.height, s.identifier "
            + "FROM measurement as m STRAIGHT_JOIN measuring_entity as me ON m.measuringentity_id = me.id "
            + "STRAIGHT_JOIN sensor_measuring_entity as sme ON me.id = sme.measuringentity_id "
            + "STRAIGHT_JOIN sensor as s ON sme.sensor_id = s.id "
            + "STRAIGHT_JOIN location as l ON l.id = s.location_id"
            + " WHERE m.datemeasurement_id "
            + " >"
            + "'"
            + startTime
            + "'"
            + " AND m.datemeasurement_id"
            + " <"
            + "'"
            + endTime
            + "'"
            + " AND m.measuringentity_id"
            + " IN (SELECT  me.id FROM measuring_entity AS me WHERE me.household_id="
            + "'"
            + household
            + "'"
            + ")";

我的大问题是:有时这个带有来自数据库的代码的应用程序运行起来真的很慢。 MySQL 运行得非常慢,但有时 MYSQL 运行得非常快。我们无法理解为什么会发生这种行为差异。

例如,当它很慢时(CPU 的 0.3-0%),从数据库中导出所有数据可能需要大约 3 天的时间(大约 200.000 个查询),但正如我之前所说,有些时候服务器在 30-40 分钟内完成相同的工作(85% 的 CPU)。

我们看到的问题是,当行为缓慢时,mysql 花费大量时间处于“准备”状态(每个查询大约 140 秒),试图优化查询,但正如我所说,这种情况发生了只有某些时候。不是每次。

1016 | root   | localhost:53936                | OptimAAL      | Query   |   10 | preparing | select HIGH_PRIORITY m.datemeasurement_id, me.type, l.place, m.value, l.room, l.height, s.identifier

这些是可以执行的查询之一:

EXPLAIN select HIGH_PRIORITY m.datemeasurement_id, me.type,
l.place,m.value, l.room, l.height, s.identifier
FROM measurement as m
STRAIGHT_JOIN measuring_entity as me ON m.measuringentity_id=me.id
STRAIGHT_JOIN sensor_measuring_entity as sme ON me.id=sme.measuringentity_id
STRAIGHT_JOIN sensor as s ON sme.sensor_id=s.id
STRAIGHT_JOIN location as l ON l.id=s.location_id
WHERE m.datemeasurement_id  >'2012-01-19 06:19:00'
AND m.datemeasurement_id <'2012-01-19 06:20:00'
AND m.measuringentity_id IN (SELECT  me.id FROM measuring_entity AS me
WHERE me.household_id='0022')
AND (height = '0')
AND (type = 'Brightness')
AND (place = 'Corner')
AND (room = 'Living room')
ORDER BY datemeasurement_id

这是解释的结果:

+----+--------------------+-------+-----------------+-----------------------------------------------+--------------------+---------+-------------------------------+------+-------------+
| id | select_type        | table | type            | possible_keys                                 | key                | key_len | ref                           | rows | Extra       |
+----+--------------------+-------+-----------------+-----------------------------------------------+--------------------+---------+-------------------------------+------+-------------+
|  1 | PRIMARY            | m     | range           | FK93F2DBBC6292BE2,FK93F2DBBCA61A7F92          | FK93F2DBBC6292BE2  | 8       | NULL                          |    4 | Using where |
|  1 | PRIMARY            | me    | eq_ref          | PRIMARY                                       | PRIMARY            | 8       | OptimAAL.m.measuringentity_id |    1 | Using where |
|  1 | PRIMARY            | sme   | ref             | PRIMARY,FK951FA3ECA61A7F92,FK951FA3ECF9AE4602 | FK951FA3ECA61A7F92 | 8       | OptimAAL.m.measuringentity_id |    1 | Using index |
|  1 | PRIMARY            | s     | eq_ref          | PRIMARY,FKCA0053BA3328FE22                    | PRIMARY            | 8       | OptimAAL.sme.sensor_id        |    1 |             |
|  1 | PRIMARY            | l     | eq_ref          | PRIMARY,place                                 | PRIMARY            | 8       | OptimAAL.s.location_id        |    1 | Using where |
|  2 | DEPENDENT SUBQUERY | me    | unique_subquery | PRIMARY,FK11C7EA07E6EB51F2                    | PRIMARY            | 8       | func                          |    1 | Using where |
+----+--------------------+-------+-----------------+-----------------------------------------------+--------------------+---------+-------------------------------+------+-------------+

显然,如果我们更改日期间隔的值,数据量会增加很多,因为我们的数据库中有大约 100 万个测量值。

我尝试了一切:

更改 mysQL 配置文件 (/etc/my.cnf):

[mysqld]
#bind-address = 141.21.8.197
max_allowed_packet = 128M
sort_buffer_size = 512M
max_connections=500
query_cache_size = 512M
query_cache_limit = 512M
query-cache-type = 2
table_cache = 80
thread_cache_size=8
key_buffer_size = 512M
read_buffer_size=64M
read_rnd_buffer_size=64M
myisam_sort_buffer_size=64M
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_size=700M
innodb_additional_mem_pool_size=20M
datadir=/data/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
#Enable logs
log = /var/log/mysql/mysql-log.log 
log-error = /var/log/mysql/mysql-error.log  
long_query_time = 1
log-slow-queries = /var/log/mysql/mysql-slow.log
[mysqld_safe]
log-error=/var/log/mysql/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
  • 使用不同类型的 JOIN 来强制它避免优化查询。
  • 使用 nice -20 为进程提供最高优先级。
  • 在代码中使用存储过程而不是查询。
  • 关闭与数据库的任何其他连接,让数据库仅供我使用,没有任何其他连接。
  • 更改并尝试优化查询。
  • ...

如您所见,我尝试了所有方法,但我不知道该怎么做才能确保服务器始终保持快速。

这些是服务器的信息:

MySQL version:  5.1.61-log / x86_64 
RAM: 8 GB
OS: CentOS release 6.2 (Final)
CPU: 4 Cores / Xeon E6510  @ 1.73GHz

非常感谢您的帮助,

编辑:

我想补充一点,现在对我来说最大的问题是为什么服务器会发生不同的行为。因为我知道可以优化查询,但有时使用这段代码工作得非常快。

我现在的噩梦是知道为什么有时工作速度很快,而不是总是。现在我正在与 IT 人员核实是否可能是硬件访问问题、硬盘问题或类似问题。

看起来也可能是 SQL 配置的问题,或者它在 MYSQL 中的查询优化器的问题,但我无法发现我的黑洞的解决方案是什么。

非常感谢您的帮助

最佳答案

我能想到您可以做的一些优化。

首先,使用绑定(bind)变量和准备好的语句。

PreparedStatment stmt = connection.prepareStatement(
"select HIGH_PRIORITY m.datemeasurement_id, me.type, l.place, m.value, l.room, l.height, s.identifier "
+ "FROM measurement as m STRAIGHT_JOIN measuring_entity as me ON m.measuringentity_id = me.id "
+ "STRAIGHT_JOIN sensor_measuring_entity as sme ON me.id = sme.measuringentity_id "
+ "STRAIGHT_JOIN sensor as s ON sme.sensor_id = s.id "
+ "STRAIGHT_JOIN location as l ON l.id = s.location_id"
+ " WHERE m.datemeasurement_id  > ? "
+ " AND m.datemeasurement_id  < ? "
+ " AND m.measuringentity_id IN (SELECT  me.id FROM measuring_entity AS me WHERE me.household_id= ? )";

stmt.setDate(1, startDate);
stmt.setDate(2, endDate);
stmt.setString(3, household);

stmt.executeQuery();

其次,删除 IN - 您不能在此处针对 measuring_entity 使用单独的联接吗? IN 通常表现不佳。

第三,你能使用批处理来执行你的插入吗?批量插入应该会显着提高速度。

这些是我能即兴想到的几件事。如果您的查询未在 Java 端进行优化,那么世界上所有的 SQL 调优都无济于事。

关于java - MySQL 在 Preparing Statement 中以非常奇怪的行为被阻止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10447009/

相关文章:

java - 最佳实践 - akka actor 中的辅助方法

java - LibGDX FrameBuffer 缩放

python - 优化此 MySQL 查询的最佳方法是什么?

mysql - 子查询优化

MySQL:如何优化这个查询?按年(日期)计算 SUM

mysql - 尝试根据事件阈值找出每月用户流失的 SQL 查询

java - 正则表达式检测字符是否重复超过三次

MySQL Cluster - mysql api 节点无法启动

Mysql - 左连接所有表

java - Struts 2 中的 TagUtils.getInstance().lookup() Struts1 的等效项