I am trying to use asynchronous queries via PHP MySQLi.
The following code has been simplified, the original is code is too verbose to list here because of class dependencies and all that. Also please assume the reference to the connection mysqli_handle has already been setup.
$query_1 = "SHOW TABLES FROM moxedo";
$query_2 = "CREATE TABLE `moxedo`.`mox_config_n85ad3` (`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT , `group_id` INT(3) UNSIGNED NOT NULL , `is_enabled` INT(1) UNSIGNED NOT NULL , `tag` VARCHAR(255) NOT NULL , `name` VARCHAR(80) NOT NULL , `value` VARCHAR(255) NOT NULL , `description` TEXT NOT NULL , `init_params` TEXT NOT NULL , `datetime_added` DATETIME NOT NULL , `datetime_lastmodified` DATETIME NOT NULL , `timestamp_univ` BIGINT(14) NOT NULL , PRIMARY KEY ( `id` ) ) ENGINE = INNODB
";
$query_3 = "ALTER TABLE `moxedo`.`mox_config_n85ad3` ADD UNIQUE `ix_u_tag_oq` ( `tag` )";
$query_4 = "SHOW TABLES FROM moxedo";
if (!$mysqli_stmt_obj = $mysqli_handle->query($query_1))
{
printf("Error: %s\n", $mysqli_handle->error);
}
if (!$mysqli_stmt_obj = $mysqli_handle->query($query_2, MYSQLI_ASYNC))
{
printf("Error: %s\n", $mysqli_handle->error);
}
if (!$mysqli_stmt_obj = $mysqli_handle->query($query_3, MYSQLI_ASYNC))
{
printf("Error: %s\n", $mysqli_handle->error);
}
if (!$mysqli_stmt_obj = $mysqli_handle->query($query_4))
{
printf("Error: %s\n", $mysqli_handle->error);
}
The call to Query 1 goes through OK. The call to Query 2 also goes through OK.
However, I'm getting "Commands out of sync; you can't run this command now" errors when I try to execute Query 3 and Query 4. From my research online I found some information on using mysqli_free_result but Query 2 and Query 3 return no resultset.
What do I need to do to properly finalize the asynchronous call so that I can make multiple calls without getting this error?
Unfortunately, the mysqli documentation is rather lacking, particularly in this regard. The issue is that the 'async' mode is a mysql client-side behavior, and not part of the client/server protocol. That is, you can still only have one query (or multi-query, I suppose) running on a connection at a given time. MYSQLI_ASYNC only specifies that your application shouldn't block while waiting for the query results. Results have to be collected later with mysqli_poll().
In your example, $query_1 is synchronous, so is completely done by the time it returns and assigns to $mysqli_stmt_obj. $query_2 is started asynchronously on $mysqli_handle successfully, and returns without waiting for the results. By the time the script gets to $query_3, it still has a pending result waiting for $query_2. Thus, it attempts to send another query before finishing the last one, giving you 'commands out of sync'.
Related
I have a very large table of IOT sample that I'm trying to run a relativly simple query against. Running the query normally using the MySql CLI returns a result in ~0.07 seconds. If I first prepare the query either via PDO or by running a SQL PREPARE statement then the request takes over a minute.
I've enabled the the optimizer trace feature, and it looks like when the statement is prepared, MySql ignores the index that it should use and does a file sort of the whole table. I'd like any insight if I am doing something wrong or if this looks like a MySql bug.
The table itself contains over 100 million samples, and at least 300 thousand are associated with the device being queried here. I ran these tests with MySql 8.0.23, but when I upgraded to 8.0.25 the issues persisted.
Table definition (some data rows ommited)
Create Table: CREATE TABLE `samples` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`organization_id` int unsigned NOT NULL,
`device_id` int unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`raw_reading` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `samples_organization_id_foreign` (`organization_id`),
KEY `samples_reverse_device_id_created_at_organization_id_index` (`device_id`,`created_at` DESC,`organization_id`),
CONSTRAINT `samples_device_id_foreign` FOREIGN KEY (`device_id`) REFERENCES `devices` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `samples_organization_id_foreign` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=188315314 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Sql That runs in < 1s
select *
from `samples`
where `samples`.`device_id` = 5852
and `samples`.`device_id` is not null
and `id` != 188315308
order by `created_at` desc
limit 1;
Sql That runs in over a minute
prepare test_prep from 'select * from `samples` where `samples`.`device_id` = ? and `samples`.`device_id` is not null and `id` != ? order by `created_at` desc limit 1';
set #a = 5852;
set #b = 188315308;
execute test_prep using #a, #b;
Trace for the non prepared SQL can be found at my gist, but the relevant part is
{
"reconsidering_access_paths_for_index_ordering": {
"clause": "ORDER BY",
"steps": [
],
"index_order_summary": {
"table": "`samples`",
"index_provides_order": true,
"order_direction": "asc",
"index": "samples_reverse_device_id_created_at_organization_id_index",
"plan_changed": false
}
}
},
Trace for the prepared query can be found at my other gist, but the relevant part is
{
"reconsidering_access_paths_for_index_ordering": {
"clause": "ORDER BY",
"steps": [
],
"index_order_summary": {
"table": "`samples`",
"index_provides_order": false,
"order_direction": "undefined",
"index": "samples_reverse_device_id_created_at_organization_id_index",
"plan_changed": false
}
}
},
The index you want to use is not that bad:
`samples_reverse_device_id_created_at_organization_id_index`
(`device_id`,`created_at` DESC,`organization_id`)
However, is not a covering index. If the query performance is really important, I would add an index that covers the filtering predicate at least. Your don't need a real covering index since you are retrieving all columns. I would try:
create index ix1 on samples (device_id, created_at, id);
EDIT
Another trick that could promote the index usage is to delay the predicate id != 188315308 as much as possible. If you know that this predicate will be matched by at least one row in the first 100 rows produced by the rest of the predicates you can try rephrasing your query as:
select *
from (
select *
from `samples`
where `samples`.`device_id` = 5852
order by `created_at` desc
limit 100
) x
where `id` != 188315308
order by `created_at` desc
limit 1
Get rid of this, since the = 5852 assures that it will be false:
and `samples`.`device_id` is not null
Then your index, or this one, should work fine.
INDEX(device_id, created_at, id)
Do not use #variables; the Optimizer seems to not look at the value they contain. That is, instead of
set #a = 5852;
set #b = 188315308;
execute test_prep using #a, #b;
Simply do
execute test_prep using 5852, 188315308;
Consider writing a bug report at bugs.mysql.com
I suspect "order_direction": "undefined" is part of the problem.
Not full solution, but a workaround. I added an index on just my timestamp and that seems to satisfy the optimizer.
KEY `samples_created_at_index` (`created_at` DESC),
I'm going to try to clean up a minimal test case and post it over on MySql bugs. I'll add a followup here if anything comes of that.
My query returns with null in my php code , but when I enter the same query into phpmyadmin it returns the row to which it belongs. Here is the database I am using
CREATE TABLE `payment`.`users`(
`u_id` int(12) NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`passwd` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY (`email`)
) ENGINE=MyISAM;
and here is the code i am using that is returning null when it clearly works in phpmyadmin.
function getUserId($email, $passwd) {
$mysqli = db_connect();
$query = "SELECT `u_id` FROM `payment`.`users` WHERE `email`='$email' AND `passwd`='$passwd' ORDER BY 1";
if ($stmt = $mysqli->prepare($query)) {
/* execute query */
$stmt->execute();
$stmt->bind_result($u_id);
while ($stmt->fetch()) {
return $u_id;
}
}
}
The thing is that you are using variables in your php code to set the values. In phpMyAdmin you're inserting values directly, therefore the problem may be in the values inserted.
First of all use PDO's bindParam() or mysqli's bind_param() statements as they sanitize inputs and help you avoid SQL Injections.
Second good thing about using prepared statements and binding params is that you can specify the type of the data being binded to to the query which in most cases will fix such problems. Though in your case you're probably inserting strings.
php
$dnumber = '9515551212';
$device_id = 'f3847010927038970110923';
device is a text field
phnum (I have used bigint, text, varchar) all give the same result.
mysql_query("INSERT INTO devices(device, phnum) VALUES('$device_id', '$dnumber')");
the above query is TRUE but the value in phnum will be empty.
mysql_query("INSERT INTO devices(device, phnum) VALUES('$device_id', $dnumber)");
the above query is FALSE, but it still inserts the record perfectly! I also get error 1064 near ")" (I removed the quotes from $dnumber)
So why is the first saying it succeeded but didnt put anything there while second says it fails but it inserted it as I wanted it to?
I think, there's something with incoming data. SQL works well.
I tried that query from php:
$sql = "INSERT INTO test(device_id, phnum) VALUES('$device_id', '$dnumber')";
$res = mysql_query($sql);
For the table with the following structure:
CREATE TABLE `test` (
`phnum` int(30) unsigned NOT NULL AUTO_INCREMENT,
`device_id` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`phnum`)
) ENGINE=InnoDB AUTO_INCREMENT=4294967295 DEFAULT CHARSET=utf8;
It worked well without any warnings. From SQL client - the same, works well.
I created the following table for user to user subscriptions.
CREATE TABLE IF NOT EXISTS `subscriptions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`subscribed_to` int(11) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_subscription` (`user_id`,`subscribed_to`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=76 ;
I'm disallowing identical rows by making the columns user_id and subscribed_to unique. If a user tries to resubmit the same data I get:
A Database Error Occurred Error Number: 1062
Duplicate entry '62-88' for key 'unique_subscription'
INSERT INTO subscriptions (user_id, subscribed_to, date) VALUES ('62',
'88', '2011-07-11 19:15:13')
Line Number: 330
I'm preventing the database error by checking if an identical row exists before trying to insert data.
$query = "SELECT COUNT(*) FROM subscriptions WHERE (user_id = '62' AND subscribed_to = '88')";
if ($query > 0)
{
//display already subscribed message
}
else
{
//insert new row
}
The database already checks the table and returns an error. The select count(*) query above seems redundant. Do I really need to check the table once more in my application? Is there a way to capture the database error if it occurs, and do something with that in my application?
If you have an idea how please share an example. I haven't a clue how this is done..!
Check out PHP function mysql_error
You can wrap the db call within a try catch statement for functions that throw exceptions to prevent your application from crashing.
PHP function mysql_query does not throw exception on error, but returns FALSE. You can check the return value and execute mysql_error to find out the trouble or log it.
I have a weird problem. Every time i execute this query in php i get the output "Challenge" even if the query is empty (should get "emptyq" if empty) when i test it in phpmyadmin everything is great and query is empty when it should be. I also tried to echo $detectChallengeRes[0][1] and got nothing. I cant find the problem, any help is very appreciated.
The script is suppose to look in the database and check if there is any challenges associated with the current userID, its basically a script that checks if a user has been challenged by another user, the gameID on the current page is the same as the one in the database and that the user hasnt completed the challenge already ($yourscore==0).
$detectChallengeRes = query("SELECT * FROM `AMCMS_challenges` WHERE `gameid`=$gameid AND `winner`=0 AND (`userkey1`=$user OR `userkey2`=$user);");
if($detectChallengeRes[0][1]!=$user && $detectChallengeRes[0][2]==$user) {
$yourscore = $detectChallengeRes[0][6]; //Check your score to see if you've already played
} elseif ($detectChallengeRes[0][2]!=$user && $detectChallengeRes[0][1]==$user) {
$yourscore = $detectChallengeRes[0][5]; //Check your score to see if you've already played
}
if ($detectChallengeRes!=NULL && $yourscore==0) {
echo 'Challenge';
} else {
echo 'emptyq';
}
Table structure:
CREATE TABLE IF NOT EXISTS `AMCMS_challenges` (
`primkey` int(11) NOT NULL auto_increment,
`userkey1` int(11) NOT NULL,
`userkey2` int(11) NOT NULL,
`gameid` int(11) NOT NULL,
`winner` int(11) NOT NULL,
`score1` int(11) NOT NULL,
`score2` int(11) NOT NULL,
PRIMARY KEY (`primkey`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
$detectChallengeRes will be boolean false or mysql result resource. It will not be ever null.
This might not solve your question but It looks like it is showing an previous data. Put this before your script
unset($detectChallengeRes);
Test for the number of rows returned by your query before trying to process it